ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilForum.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use ILIAS\User\Profile\Visibility as PersonalProfileVisbility;
22
30{
31 private const int SORT_TITLE = 1;
32 private const int SORT_DATE = 2;
33 private const int DEFAULT_PAGE_HITS = 30;
34
36 protected static array $moderators_by_ref_id_map = [];
37
38 private readonly ilAppEventHandler $event;
39 private string $dbTable;
40 private string $className = 'ilForum';
41 private string $mdb2Query = '';
42 private array $mdb2DataValue = [];
43 private array $mdb2DataType = [];
44 private string $txtQuote1 = "[quote]";
45 private string $txtQuote2 = "[/quote]";
46 private string $replQuote1 = '<blockquote class="ilForumQuote">';
47 private string $replQuote2 = '</blockquote>';
49 private int $id;
50 private int $ref_id;
51 private string $import_name = '';
59
60 public function __construct()
61 {
62 global $DIC;
63
64 $this->error = $DIC['ilErr'];
65 $this->lng = $DIC->language();
66 $this->db = $DIC->database();
67 $this->user = $DIC->user();
68 $this->settings = $DIC->settings();
69 $this->event = $DIC->event();
70 $this->ui_factory = $DIC->ui()->factory();
71 $this->ui_renderer = $DIC->ui()->renderer();
72 }
73
74 public function setForumId(int $a_obj_id): void
75 {
76 $this->id = $a_obj_id;
77 }
78
79 public function setForumRefId(int $a_ref_id): void
80 {
81 $this->ref_id = $a_ref_id;
82 }
83
84 public function getForumId(): int
85 {
86 return $this->id;
87 }
88
89 public function getForumRefId(): int
90 {
91 return $this->ref_id;
92 }
93
94 public function setDbTable(string $dbTable): void
95 {
96 if ($dbTable === '') {
97 die($this->className . '::setDbTable(): No database table given.');
98 }
99
100 $this->dbTable = $dbTable;
101 }
102
103 public function getDbTable(): string
104 {
105 return $this->dbTable;
106 }
107
108 public function setMDB2WhereCondition(string $query_string, array $data_type, array $data_value): bool
109 {
110 $this->mdb2Query = $query_string;
111 $this->mdb2DataValue = $data_value;
112 $this->mdb2DataType = $data_type;
113
114 return true;
115 }
116
117 public function getMDB2Query(): string
118 {
119 return $this->mdb2Query ?: '';
120 }
121
122 public function getMDB2DataValue(): array
123 {
124 return $this->mdb2DataValue ?: [];
125 }
126
127 public function getMDB2DataType(): array
128 {
129 return $this->mdb2DataType ?: [];
130 }
131
132 public function setPageHits(int $pageHits): bool
133 {
134 if ($pageHits < 1) {
135 $pageHits = 1;
136 }
137
138 $this->pageHits = $pageHits;
139 return true;
140 }
141
142 public function getPageHits(): int
143 {
144 return $this->pageHits;
145 }
146
147 public function getOneTopic(): ForumDto
148 {
149 $data_type = [];
150 $data_value = [];
151
152 $query = 'SELECT * FROM frm_data WHERE ';
153 if ($this->getMDB2Query() !== '' && $this->getMDB2DataType() !== [] && $this->getMDB2DataValue() !== []) {
154 $query .= ' ' . $this->getMDB2Query() . ' ';
155 $data_type += $this->getMDB2DataType();
156 $data_value += $this->getMDB2DataValue();
157 } else {
158 $query .= '1 = 1';
159 }
160
161 $res = $this->db->queryF($query, $data_type, $data_value);
162 $row = $this->db->fetchAssoc($res);
163
164 if (!is_array($row) || $row === []) {
166 }
167
169 }
170
171 public function getOneThread(): ilForumTopic
172 {
173 $data_type = [];
174 $data_value = [];
175
176 $query = 'SELECT * FROM frm_threads WHERE ';
177 if ($this->getMDB2Query() !== '' && $this->getMDB2DataType() !== [] && $this->getMDB2DataValue() !== []) {
178 $query .= ' ' . $this->getMDB2Query() . ' ';
179 $data_type += $this->getMDB2DataType();
180 $data_value += $this->getMDB2DataValue();
181 } else {
182 $query .= '1 = 1';
183 }
184
185 $sql_res = $this->db->queryF($query, $data_type, $data_value);
186 $result = $this->db->fetchAssoc($sql_res);
187 $result['thr_subject'] = trim((string) ($result['thr_subject'] ?? ''));
188
189 $thread_obj = new ilForumTopic();
190 $thread_obj->assignData($result);
191
192 return $thread_obj;
193 }
194
195 public function generatePost(
196 int $forum_id,
197 int $thread_id,
198 int $author_id,
199 int $display_user_id,
200 string $message,
201 int $parent_pos,
202 bool $notify,
203 string $subject = '',
204 ?string $alias = null,
205 string $date = '',
206 bool $status = true,
207 bool $send_activation_mail = false
208 ): int {
209 $objNewPost = new ilForumPost();
210 $objNewPost->setForumId($forum_id);
211 $objNewPost->setThreadId($thread_id);
212 $objNewPost->setSubject($subject);
213 $objNewPost->setMessage($message);
214 $objNewPost->setDisplayUserId($display_user_id);
215 $objNewPost->setUserAlias($alias);
216 $objNewPost->setPosAuthorId($author_id);
217
218 $frm_settings = ilForumProperties::getInstance($this->getForumId());
219
220 $is_moderator = false;
221 if ($frm_settings->getMarkModeratorPosts() && self::_isModerator($this->getForumRefId(), $author_id)) {
222 $is_moderator = true;
223 }
224 $objNewPost->setIsAuthorModerator($is_moderator);
225
226 if ($date === '') {
227 $objNewPost->setCreateDate(date('Y-m-d H:i:s'));
228 } elseif (strpos($date, '-') > 0) {
229 $objNewPost->setCreateDate($date);
230 }
231
232 if ($status) {
233 $objNewPost->setPostActivationDate($objNewPost->getCreateDate());
234 }
235
236 $objNewPost->setImportName($this->getImportName());
237 $objNewPost->setNotification($notify);
238 $objNewPost->setStatus($status);
239 $objNewPost->insert();
240
241 if ($parent_pos === 0) {
242 $this->addPostTree($objNewPost->getThreadId(), $objNewPost->getId(), $objNewPost->getCreateDate());
243 } else {
244 $this->insertPostNode(
245 $objNewPost->getId(),
246 $parent_pos,
247 $objNewPost->getThreadId(),
248 $objNewPost->getCreateDate()
249 );
250 }
251
252 $lastPost = $objNewPost->getForumId() . '#' . $objNewPost->getThreadId() . '#' . $objNewPost->getId();
253
254 $this->db->manipulateF(
255 'UPDATE frm_threads SET thr_num_posts = thr_num_posts + 1, thr_last_post = %s WHERE thr_pk = %s',
256 ['text', 'integer'],
257 [$lastPost, $objNewPost->getThreadId()]
258 );
259
260 $this->db->manipulateF(
261 'UPDATE frm_data SET top_num_posts = top_num_posts + 1, top_last_post = %s WHERE top_pk = %s',
262 ['text', 'integer'],
263 [$lastPost, $objNewPost->getForumId()]
264 );
265
268 $forum_obj->markPostRead($objNewPost->getPosAuthorId(), $objNewPost->getThreadId(), $objNewPost->getId());
269
270 if ($status && $parent_pos > 0) {
271 $news_item = new ilNewsItem();
272 $news_item->setContext($forum_obj->getId(), 'frm', $objNewPost->getId(), 'pos');
273 $news_item->setPriority(NEWS_NOTICE);
274 $news_item->setTitle($objNewPost->getSubject());
275 $news_item->setContent(ilRTE::_replaceMediaObjectImageSrc(
276 $this->prepareText($objNewPost->getMessage(), 0),
277 1
278 ));
279 if ($objNewPost->getMessage() !== strip_tags($objNewPost->getMessage())) {
280 $news_item->setContentHtml(true);
281 }
282
283 $news_item->setUserId($display_user_id);
284 $news_item->setVisibility(NEWS_USERS);
285 $news_item->create();
286 }
287
288 return $objNewPost->getId();
289 }
290
291 public function generateThread(
292 ilForumTopic $thread,
293 string $message,
294 bool $notify,
295 bool $notify_posts,
296 bool $status = true,
297 bool $withFirstVisibleEntry = true
298 ): int {
299 if (!$thread->getCreateDate()) {
300 $thread->setCreateDate(date('Y-m-d H:i:s'));
301 }
302
303 $thread->setImportName($this->getImportName());
304 $thread->insert();
305
306 if ($notify_posts) {
307 $thread->enableNotification($thread->getThrAuthorId());
308 }
309
310 $this->db->manipulateF(
311 'UPDATE frm_data SET top_num_threads = top_num_threads + 1 WHERE top_pk = %s',
312 ['integer'],
313 [$thread->getForumId()]
314 );
315
316 $rootNodeId = $this->generatePost(
317 $thread->getForumId(),
318 $thread->getId(),
319 $thread->getThrAuthorId(),
320 $thread->getDisplayUserId(),
321 '',
322 0,
323 false,
324 $thread->getSubject(),
325 $thread->getUserAlias(),
326 $thread->getCreateDate(),
327 true,
328 false
329 );
330
331 if (!$withFirstVisibleEntry) {
332 return $rootNodeId;
333 }
334
335 return $this->generatePost(
336 $thread->getForumId(),
337 $thread->getId(),
338 $thread->getThrAuthorId(),
339 $thread->getDisplayUserId(),
340 $message,
341 $rootNodeId,
342 $notify,
343 $thread->getSubject(),
344 $thread->getUserAlias(),
345 $thread->getCreateDate(),
346 $status,
347 false
348 );
349 }
350
355 public function moveThreads(array $thread_ids, ilObjForum $src_forum, int $target_obj_id): array
356 {
357 $errorMessages = [];
358
359 if ($target_obj_id <= 0 || $src_forum->getId() <= 0) {
360 return $errorMessages;
361 }
362
363 $this->setMDB2WhereCondition('top_frm_fk = %s ', ['integer'], [$src_forum->getId()]);
364 $oldFrmData = $this->getOneTopic();
365
366 $this->setMDB2WhereCondition('top_frm_fk = %s ', ['integer'], [$target_obj_id]);
367 $newFrmData = $this->getOneTopic();
368
369 if (!$oldFrmData->getTopPk() || !$newFrmData->getTopPk()) {
370 return $errorMessages;
371 }
372
373 $num_moved_posts = 0;
374 $num_moved_threads = 0;
375 $num_visits = 0;
376
377 foreach ($thread_ids as $id) {
378 $objTmpThread = new ilForumTopic($id);
379
380 try {
381 $numPosts = $objTmpThread->movePosts(
382 $src_forum->getId(),
383 $oldFrmData->getTopPk(),
384 $target_obj_id,
385 $newFrmData->getTopPk()
386 );
387
388 if (($last_post_string = ($objTmpThread->getLastPostString() ?? '')) !== '') {
389 $last_post_string = explode('#', $last_post_string);
390 $last_post_string[0] = $newFrmData->getTopPk();
391 $last_post_string = implode('#', $last_post_string);
392 $objTmpThread->setLastPostString($last_post_string);
393 }
394
395 $num_visits += $objTmpThread->getVisits();
396 $num_moved_posts += $numPosts;
397 ++$num_moved_threads;
398
399 $objTmpThread->setForumId($newFrmData->getTopPk());
400 $objTmpThread->update();
401 } catch (ilFileUtilsException) {
402 $errorMessages[] = sprintf($this->lng->txt('frm_move_invalid_file_type'), $objTmpThread->getSubject());
403 continue;
404 }
405 }
406
407 if (0 === max($num_moved_threads, $num_moved_posts, $num_visits)) {
408 return $errorMessages;
409 }
410
411 $this->db->setLimit(1, 0);
412 $res = $this->db->queryF(
413 'SELECT pos_thr_fk, pos_pk FROM frm_posts WHERE pos_top_fk = %s ORDER BY pos_date DESC',
414 ['integer'],
415 [$oldFrmData->getTopPk()]
416 );
417
418 $last_post_src = '';
419 $row = $this->db->fetchObject($res);
420 if ($row !== null) {
421 $last_post_src = $oldFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk;
422 }
423
424 $this->db->manipulateF(
425 'UPDATE frm_data ' .
426 'SET top_num_posts = top_num_posts - %s, top_num_threads = top_num_threads - %s, visits = visits - %s, ' .
427 'top_last_post = %s WHERE top_pk = %s',
428 ['integer', 'integer', 'integer', 'text', 'integer'],
429 [
430 $num_moved_posts,
431 $num_moved_threads,
432 $num_visits,
433 $last_post_src,
434 $oldFrmData->getTopPk()
435 ]
436 );
437
438 $this->db->setLimit(1, 0);
439 $res = $this->db->queryF(
440 'SELECT pos_thr_fk, pos_pk FROM frm_posts WHERE pos_top_fk = %s ORDER BY pos_date DESC',
441 ['integer'],
442 [$newFrmData->getTopPk()]
443 );
444
445 $last_post_dest = '';
446 $row = $this->db->fetchObject($res);
447 if ($row !== null) {
448 $last_post_dest = $newFrmData->getTopPk() . '#' . $row->pos_thr_fk . '#' . $row->pos_pk;
449 }
450
451 $this->db->manipulateF(
452 'UPDATE frm_data SET top_num_posts = top_num_posts + %s, top_num_threads = top_num_threads + %s, ' .
453 'visits = visits + %s, top_last_post = %s WHERE top_pk = %s',
454 ['integer', 'integer', 'integer', 'text', 'integer'],
455 [$num_moved_posts, $num_moved_threads, $num_visits, $last_post_dest, $newFrmData->getTopPk()]
456 );
457
458 $this->event->raise(
459 'components/ILIAS/Forum',
460 'movedThreads',
461 [
462 'source_ref_id' => $src_forum->getId(),
463 'target_ref_id' => $src_forum->getId(),
464 'thread_ids' => $src_forum->getId(),
465 'source_frm_obj_id' => $src_forum->getId(),
466 'target_frm_obj_id' => $target_obj_id
467 ]
468 );
469
470 return $errorMessages;
471 }
472
473 public function postCensorship(ilObjForum $forum, string $message, int $pos_pk, int $cens = 0): void
474 {
475 $cens_date = date('Y-m-d H:i:s');
476
477 $this->db->manipulateF(
478 'UPDATE frm_posts
479 SET pos_cens_com = %s,
480 pos_cens_date = %s,
481 pos_cens = %s,
482 update_user = %s
483 WHERE pos_pk = %s',
484 ['text', 'timestamp', 'integer', 'integer', 'integer'],
485 [$message,
486 $cens_date,
487 $cens,
488 $this->user->getId(),
489 $pos_pk
490 ]
491 );
492
494 $this->id,
495 'frm',
496 $pos_pk,
497 'pos'
498 );
499 if ($news_id > 0) {
500 if ($cens > 0) {
501 $news_item = new ilNewsItem($news_id);
502 $news_item->setContent(nl2br($this->prepareText($message, 0)));
503 $news_item->setContentHtml(false);
504 if ($message !== strip_tags($message)) {
505 $news_item->setContentHtml(true);
506 }
507 } else {
508 $res = $this->db->queryF('SELECT pos_message FROM frm_posts WHERE pos_pk = %s', ['integer'], [$pos_pk]);
509 $rec = $this->db->fetchAssoc($res);
510
511 $news_item = new ilNewsItem($news_id);
512 $news_item->setContent(nl2br($this->prepareText($rec['pos_message'], 0)));
513 $news_item->setContentHtml(false);
514 if ($rec['pos_message'] !== strip_tags($rec['pos_message'])) {
515 $news_item->setContentHtml(true);
516 }
517 }
518 $news_item->update();
519 }
520
521 $this->event->raise(
522 'components/ILIAS/Forum',
523 'censoredPost',
524 [
525 'ref_id' => $this->getForumRefId(),
526 'post' => new ilForumPost($pos_pk),
527 'object' => $forum
528 ]
529 );
530 }
531
535 public function deletePost($postIdOrRecord, bool $raiseEvents = true): int
536 {
537 $p_node = $postIdOrRecord;
538 if (is_numeric($postIdOrRecord)) {
539 $p_node = $this->getPostNode($postIdOrRecord);
540 }
541
542 $post = new ilForumPost((int) $p_node['pos_pk']);
543 if ($raiseEvents) {
544 $is_deleted_thread = ($post->getParentId() == 0) ? true : false;
545 $num_visible_active_posts = 0;
546 if ($is_deleted_thread) {
547 $query = '
548 SELECT COUNT(*) AS cnt
549 FROM frm_posts
550 INNER JOIN frm_posts_tree ON pos_pk = pos_fk
551 WHERE frm_posts_tree.parent_pos != 0
552 AND pos_thr_fk = ' . $this->db->quote($post->getThreadId(), 'integer') . '
553 AND pos_status = ' . $this->db->quote(1, 'integer');
554 $res = $this->db->query($query);
555 $row = $this->db->fetchAssoc($res);
556 $num_visible_active_posts = (int) ($row['cnt'] ?? 0);
557 }
558
559 $this->event->raise(
560 'components/ILIAS/Forum',
561 'beforePostDeletion',
562 [
563 'obj_id' => $this->getForumId(),
564 'ref_id' => $this->getForumRefId(),
565 'post' => $post,
566 'thread_deleted' => $is_deleted_thread,
567 'num_visible_active_posts' => $num_visible_active_posts
568 ]
569 );
570 }
571
572 $this->deletePostFiles(array_merge(
573 $this->getSubPathIdsForNode($post),
574 [$post->getId()]
575 ));
576
577 $affected_user_ids[] = $post->getPosAuthorId();
578 $deleted_post_ids = $this->deletePostTree($p_node);
579
580 $obj_history = new ilForumDraftsHistory();
581 $obj_history->deleteHistoryByPostIds($deleted_post_ids);
582
583 $obj_draft = new ilForumPostDraft();
584 $obj_draft->deleteDraftsByPostIds($deleted_post_ids);
585
586 foreach ($deleted_post_ids as $post_id) {
588 }
589
590 $dead_pos = count($deleted_post_ids);
591 $dead_thr = 0;
592
593 if ((int) $post->getParentId() === 0) {
594 $dead_thr = $post->getThreadId();
595
596 $this->db->manipulateF('DELETE FROM frm_threads WHERE thr_pk = %s', ['integer'], [$dead_thr]);
597 $this->db->manipulateF(
598 'UPDATE frm_data SET top_num_threads = top_num_threads - 1 WHERE top_frm_fk = %s',
599 ['integer'],
600 [$this->id]
601 );
602
603 $posset = $this->db->queryF('SELECT * FROM frm_posts WHERE pos_thr_fk = %s', ['integer'], [$dead_thr]);
604 while ($posrec = $this->db->fetchAssoc($posset)) {
606 $this->id,
607 'frm',
608 (int) $posrec['pos_pk'],
609 'pos'
610 );
611 if ($news_id > 0) {
612 $news_item = new ilNewsItem($news_id);
613 $news_item->delete();
614 }
615
616 try {
617 $mobs = ilObjMediaObject::_getMobsOfObject('frm:html', (int) $posrec['pos_pk']);
618 foreach ($mobs as $mob) {
619 if (ilObjMediaObject::_exists($mob)) {
620 ilObjMediaObject::_removeUsage($mob, 'frm:html', (int) $posrec['pos_pk']);
621 $mob_obj = new ilObjMediaObject($mob);
622 $mob_obj->delete();
623 }
624 }
625 } catch (Exception) {
626 }
627 $affected_user_ids[] = (int) $posrec['pos_author_id'];
628 }
629
630 $this->db->manipulateF('DELETE FROM frm_posts WHERE pos_thr_fk = %s', ['integer'], [$post->getThreadId()]);
631 } else {
632 for ($i = 0; $i < $dead_pos; $i++) {
633 $this->db->manipulateF('DELETE FROM frm_posts WHERE pos_pk = %s', ['integer'], [$deleted_post_ids[$i]]);
634
636 $this->id,
637 'frm',
638 $deleted_post_ids[$i],
639 'pos'
640 );
641 if ($news_id > 0) {
642 $news_item = new ilNewsItem($news_id);
643 $news_item->delete();
644 }
645
646 try {
647 $mobs = ilObjMediaObject::_getMobsOfObject('frm:html', $deleted_post_ids[$i]);
648 foreach ($mobs as $mob) {
649 if (ilObjMediaObject::_exists($mob)) {
650 ilObjMediaObject::_removeUsage($mob, 'frm:html', $deleted_post_ids[$i]);
651 $mob_obj = new ilObjMediaObject($mob);
652 $mob_obj->delete();
653 }
654 }
655 } catch (Exception) {
656 }
657 }
658
659 $this->db->manipulateF(
660 'UPDATE frm_threads SET thr_num_posts = thr_num_posts - %s WHERE thr_pk = %s',
661 ['integer', 'integer'],
662 [$dead_pos, $post->getTreeId()]
663 );
664
665 $res1 = $this->db->queryF(
666 'SELECT * FROM frm_posts WHERE pos_thr_fk = %s ORDER BY pos_date DESC',
667 ['integer'],
668 [$post->getTreeId()]
669 );
670
671 $lastPost_thr = '';
672 if ($res1->numRows() > 0) {
673 $z = 0;
674
675 while ($selData = $this->db->fetchAssoc($res1)) {
676 if ($z > 0) {
677 break;
678 }
679
680 $lastPost_thr = $selData['pos_top_fk'] . '#' . $selData['pos_thr_fk'] . '#' . $selData['pos_pk'];
681 $z++;
682 }
683 }
684
685 $this->db->manipulateF(
686 'UPDATE frm_threads SET thr_last_post = %s WHERE thr_pk = %s',
687 ['text', 'integer'],
688 [$lastPost_thr, $post->getTreeId()]
689 );
690 }
691
692 $this->db->manipulateF(
693 'UPDATE frm_data SET top_num_posts = top_num_posts - %s WHERE top_frm_fk = %s',
694 ['integer', 'integer'],
695 [$dead_pos, $this->id]
696 );
697
698 $res2 = $this->db->queryF(
699 'SELECT * FROM frm_posts, frm_data WHERE pos_top_fk = top_pk AND top_frm_fk = %s ORDER BY pos_date DESC',
700 ['integer'],
701 [$this->id]
702 );
703
704 $lastPost_top = '';
705 if ($res2->numRows() > 0) {
706 $z = 0;
707
708 while ($selData = $this->db->fetchAssoc($res2)) {
709 if ($z > 0) {
710 break;
711 }
712
713 $lastPost_top = $selData['pos_top_fk'] . '#' . $selData['pos_thr_fk'] . '#' . $selData['pos_pk'];
714 $z++;
715 }
716 }
717
718 $this->db->manipulateF(
719 'UPDATE frm_data SET top_last_post = %s WHERE top_frm_fk = %s',
720 ['text', 'integer'],
721 [$lastPost_top, $this->id]
722 );
723
724 if ($raiseEvents) {
725 $this->event->raise(
726 'components/ILIAS/Forum',
727 'afterPostDeletion',
728 [
729 'obj_id' => $this->getForumId(),
730 'ref_id' => $this->getForumRefId(),
731 'user_ids' => $affected_user_ids
732 ]
733 );
734 }
735
736 return $dead_thr;
737 }
738
742 public function getAllThreads(int $a_topic_id, array $params = [], int $limit = 0, int $offset = 0): array
743 {
744 $frm_props = ilForumProperties::getInstance($this->getForumId());
745 $is_post_activation_enabled = $frm_props->isPostActivationEnabled();
746
747 $user_id = $this->user->getId();
748
749 $excluded_ids_condition = '';
750 if (isset($params['excluded_ids']) && is_array($params['excluded_ids']) && $params['excluded_ids']) {
751 $excluded_ids_condition = ' AND ' . $this->db->in('thr_pk', $params['excluded_ids'], true, 'integer') . ' ';
752 }
753
754 if (!isset($params['order_column']) || !in_array(
755 strtolower($params['order_column']),
756 ['lp_date', 'rating', 'thr_subject', 'num_posts', 'num_visit']
757 )) {
758 $params['order_column'] = 'post_date';
759 }
760 if (!isset($params['order_direction']) || !in_array(strtolower($params['order_direction']), ['asc', 'desc'])) {
761 $params['order_direction'] = 'desc';
762 }
763
764 $cnt_active_pos_query = '';
765 $cnt_join_type = 'LEFT';
766 if ($is_post_activation_enabled && !$params['is_moderator']) {
767 $cnt_active_pos_query = " AND (pos_status = {$this->db->quote(1, 'integer')} OR pos_author_id = {$this->db->quote($user_id, 'integer')}) ";
768 $cnt_join_type = "INNER";
769 }
770 $query =
771 "SELECT COUNT(DISTINCT(thr_pk)) cnt
772 FROM frm_threads
773 $cnt_join_type JOIN frm_posts
774 ON pos_thr_fk = thr_pk $cnt_active_pos_query
775 WHERE thr_top_fk = %s $excluded_ids_condition
776 ";
777 $res = $this->db->queryF($query, ['integer'], [$a_topic_id]);
778 $cntData = $this->db->fetchAssoc($res);
779 $cnt = (int) $cntData['cnt'];
780
781 $active_query = '';
782 $having = '';
783 if ($is_post_activation_enabled && !$params['is_moderator']) {
784 $active_query = ' AND (pos_status = %s OR pos_author_id = %s) ';
785 $having = ' HAVING num_posts > 0';
786 }
787
788 $threads = [];
789 $data = [];
790 $data_types = [];
791
792 $optional_fields = '';
793 if ($frm_props->isIsThreadRatingEnabled()) {
794 $optional_fields = ', avg_rating';
795 }
796
797 $additional_sort = '';
798
799 if ($params['order_column'] === 'thr_subject') {
800 $dynamic_columns = [', thr_subject ' . $params['order_direction']];
801 } elseif ($params['order_column'] === 'num_posts') {
802 $dynamic_columns = [', num_posts ' . $params['order_direction']];
803 } elseif ($params['order_column'] === 'num_visit') {
804 $dynamic_columns = [', visits ' . $params['order_direction']];
805 } else {
806 $dynamic_columns = [', post_date ' . $params['order_direction']];
807 }
808
809 if ($frm_props->isIsThreadRatingEnabled()) {
810 $dynamic_columns[] = ' ,avg_rating ' . $params['order_direction'];
811 }
812 if ('rating' === strtolower($params['order_column'])) {
813 $dynamic_columns = array_reverse($dynamic_columns);
814 }
815 $additional_sort .= implode(' ', $dynamic_columns);
816
817
818 if (!$this->user->isAnonymous()) {
819 $query = "SELECT
820 (CASE WHEN COUNT(DISTINCT(notification_id)) > 0 THEN 1 ELSE 0 END) usr_notification_is_enabled,
821 MAX(pos_date) post_date,
822 SUM(tree1.parent_pos != 0) num_posts,
823 SUM(tree1.parent_pos != 0) - SUM(tree1.parent_pos != 0 AND postread.post_id IS NOT NULL) num_unread_posts, ";
824
825 $query .= " thr_pk, thr_top_fk, thr_subject, thr_author_id, thr_display_user_id, thr_usr_alias, thr_num_posts, thr_last_post, thr_date, thr_update, visits, frm_threads.import_name, is_sticky, is_closed
826 $optional_fields
827 FROM frm_threads
828
829 LEFT JOIN frm_notification
830 ON frm_notification.thread_id = thr_pk
831 AND frm_notification.user_id = %s
832
833 LEFT JOIN frm_posts
834 ON pos_thr_fk = thr_pk $active_query
835 LEFT JOIN frm_posts_tree tree1
836 ON tree1.pos_fk = frm_posts.pos_pk
837 LEFT JOIN frm_user_read postread
838 ON postread.post_id = pos_pk
839 AND postread.usr_id = %s";
840
841 $query .= " WHERE thr_top_fk = %s
842 $excluded_ids_condition
843 GROUP BY thr_pk, thr_top_fk, thr_subject, thr_author_id, thr_display_user_id, thr_usr_alias, thr_num_posts, thr_last_post, thr_date, thr_update, visits, frm_threads.import_name, is_sticky, is_closed
844 $optional_fields
845 $having
846 ORDER BY is_sticky DESC $additional_sort, thr_date DESC";
847
848
849 $data_types[] = 'integer';
850 if ($is_post_activation_enabled && !$params['is_moderator']) {
851 $data_types[] = 'integer';
852 $data_types[] = 'integer';
853 }
854 $data_types[] = 'integer';
855 $data_types[] = 'integer';
856
857
858 $data[] = $user_id;
859 if ($is_post_activation_enabled && !$params['is_moderator']) {
860 $data[] = 1;
861 $data[] = $user_id;
862 }
863 $data[] = $user_id;
864 } else {
865 $query = "SELECT
866 0 usr_notification_is_enabled,
867 MAX(pos_date) post_date,
868 COUNT(DISTINCT(tree1.pos_fk)) num_posts,
869 COUNT(DISTINCT(tree1.pos_fk)) num_unread_posts,
870 thr_pk, thr_top_fk, thr_subject, thr_author_id, thr_display_user_id, thr_usr_alias, thr_num_posts, thr_last_post, thr_date, thr_update, visits, frm_threads.import_name, is_sticky, is_closed
871 $optional_fields
872 FROM frm_threads
873
874 LEFT JOIN frm_posts
875 ON pos_thr_fk = thr_pk $active_query
876 LEFT JOIN frm_posts_tree tree1
877 ON tree1.pos_fk = frm_posts.pos_pk AND tree1.parent_pos != 0
878 ";
879
880 $query .= " WHERE thr_top_fk = %s
881 $excluded_ids_condition
882 GROUP BY thr_pk, thr_top_fk, thr_subject, thr_author_id, thr_display_user_id, thr_usr_alias, thr_num_posts, thr_last_post, thr_date, thr_update, visits, frm_threads.import_name, is_sticky, is_closed
883 $optional_fields
884 $having
885 ORDER BY is_sticky DESC $additional_sort, thr_date DESC";
886
887 if ($is_post_activation_enabled && !$params['is_moderator']) {
888 $data_types[] = 'integer';
889 $data_types[] = 'integer';
890 }
891 $data_types[] = 'integer';
892 if ($is_post_activation_enabled && !$params['is_moderator']) {
893 $data[] = 1;
894 $data[] = $user_id;
895 }
896 }
897 $data[] = $a_topic_id;
898
899 if ($limit || $offset) {
900 $this->db->setLimit($limit, $offset);
901 }
902
903 $threadIds = [];
904 $res = $this->db->queryF($query, $data_types, $data);
905 while ($row = $this->db->fetchAssoc($res)) {
906 $thread = new ilForumTopic((int) $row['thr_pk'], (bool) $params['is_moderator'], true);
907 $thread->assignData($row);
908 $threads[(int) $row['thr_pk']] = $thread;
909 $threadIds[] = (int) $row['thr_pk'];
910 }
911
912 $inner_last_active_post_condition = '';
913 if ($is_post_activation_enabled && !$params['is_moderator']) {
914 $inner_last_active_post_condition = sprintf(
915 ' AND (iposts.pos_status = %s OR (iposts.pos_status = %s AND iposts.pos_author_id = %s)) ',
916 $this->db->quote(1, 'integer'),
917 $this->db->quote(0, 'integer'),
918 $this->db->quote($this->user->getId(), 'integer')
919 );
920 }
921
922 $post_res = $this->db->query(
923 'SELECT frm_posts.*
924 FROM frm_posts
925 INNER JOIN (
926 SELECT pos_thr_fk, MAX(iposts.pos_date) i_pos_date
927 FROM frm_posts iposts
928 WHERE ' . $this->db->in('iposts.pos_thr_fk', $threadIds, false, 'integer') . '
929 ' . $inner_last_active_post_condition . '
930 GROUP BY pos_thr_fk
931 ) opost ON frm_posts.pos_thr_fk = opost.pos_thr_fk AND frm_posts.pos_date = opost.i_pos_date'
932 );
933 while ($post_row = $this->db->fetchAssoc($post_res)) {
934 $tmp_obj = new ilForumPost((int) $post_row['pos_pk'], (bool) $params['is_moderator'], true);
935 $tmp_obj->setPosAuthorId((int) $post_row['pos_author_id']);
936 $tmp_obj->setDisplayUserId((int) $post_row['pos_display_user_id']);
937 $tmp_obj->setUserAlias((string) $post_row['pos_usr_alias']);
938 $tmp_obj->setImportName((string) $post_row['import_name']);
939 $tmp_obj->setId((int) $post_row['pos_pk']);
940 $tmp_obj->setCreateDate((string) $post_row['pos_date']);
941
942 $threads[(int) $post_row['pos_thr_fk']]->setLastPostForThreadOverview($tmp_obj);
943 }
944
945 return [
946 'items' => $threads,
947 'cnt' => $cnt
948 ];
949 }
950
951 public function getNumberOfPublishedUserPostings(int $usr_id, bool $post_activation_required): int
952 {
953 $query = '
954 SELECT
955 SUM(IF(f.pos_cens = %s, 1, 0)) cnt
956 FROM frm_posts f
957 INNER JOIN frm_posts_tree t ON f.pos_pk = t.pos_fk AND t.parent_pos != %s
958 INNER JOIN frm_threads th ON t.thr_fk = th.thr_pk
959 INNER JOIN frm_data d ON d.top_pk = f.pos_top_fk AND d.top_frm_fk = %s
960 WHERE f.pos_author_id = %s
961 ';
962
963 if ($post_activation_required) {
964 $query .= ' AND f.pos_status = ' . $this->db->quote(1, 'integer');
965 }
966
967 $res = $this->db->queryF(
968 $query,
969 ['integer', 'integer', 'integer', 'integer'],
970 [0, 0, $this->getForumId(), $usr_id]
971 );
972 $row = $this->db->fetchAssoc($res);
973 if (is_array($row)) {
974 return (int) $row['cnt'];
975 }
976
977 return 0;
978 }
979
983 public function getUserStatistics(bool $post_activation_required): array
984 {
985 $statistic = [];
986 $data_types = [];
987 $data = [];
988
989 $query = '
990 SELECT
991 u.login, u.lastname, u.firstname, f.pos_author_id, u.usr_id,
992 p.value public_profile,
993 SUM(IF(f.pos_cens = %s, 1, 0)) num_postings
994 FROM frm_posts f
995 INNER JOIN frm_posts_tree t ON f.pos_pk = t.pos_fk
996 INNER JOIN frm_threads th ON t.thr_fk = th.thr_pk
997 INNER JOIN usr_data u ON u.usr_id = f.pos_author_id
998 INNER JOIN frm_data d ON d.top_pk = f.pos_top_fk
999 LEFT JOIN usr_pref p ON p.usr_id = u.usr_id AND p.keyword = %s
1000 WHERE t.parent_pos != %s
1001 ';
1002
1003 $data_types[] = 'integer';
1004 $data_types[] = 'text';
1005 $data_types[] = 'integer';
1006 $data[] = 0;
1007 $data[] = 'public_profile';
1008 $data[] = 0;
1009
1010 if ($post_activation_required) {
1011 $query .= ' AND pos_status = %s';
1012 $data_types[] = 'integer';
1013 $data[] = 1;
1014 }
1015
1016 $query .= '
1017 AND d.top_frm_fk = %s
1018 GROUP BY u.login, p.value,u.lastname, u.firstname, f.pos_author_id
1019 ';
1020
1021 $data_types[] = 'integer';
1022 $data[] = $this->getForumId();
1023
1024 $res = $this->db->queryF($query, $data_types, $data);
1025 while ($row = $this->db->fetchAssoc($res)) {
1026 if (
1027 !in_array($row['public_profile'], [
1028 PersonalProfileVisbility::PROFILE_ENABLED_LOGGED_IN_USERS,
1029 PersonalProfileVisbility::PROFILE_ENABLED_GLOBAL], true)
1030 || ($this->user->isAnonymous() && $row['public_profile'] !== PersonalProfileVisbility::PROFILE_ENABLED_GLOBAL)
1031 ) {
1032 $row['lastname'] = '';
1033 $row['firstname'] = '';
1034 }
1035
1036 $row['usr_id'] = (int) $row['usr_id'];
1037 $row['pos_author_id'] = (int) $row['pos_author_id'];
1038 $row['num_postings'] = (int) $row['num_postings'];
1039
1040 $statistic[] = $row;
1041 }
1042
1043 return $statistic;
1044 }
1045
1046 public function getRootPostIdByThread(int $a_thread_id): int
1047 {
1048 $res = $this->db->queryF(
1049 'SELECT pos_fk FROM frm_posts_tree WHERE thr_fk = %s AND parent_pos = %s',
1050 ['integer', 'integer'],
1051 [$a_thread_id, 0]
1052 );
1053
1054 $row = $this->db->fetchObject($res);
1055 if ($row instanceof stdClass) {
1056 return (int) $row->pos_fk;
1057 }
1058
1059 return 0;
1060 }
1061
1065 public function getModerators(): array
1066 {
1067 return self::_getModerators($this->getForumRefId());
1068 }
1069
1073 public static function _getModerators(int $a_ref_id): array
1074 {
1075 global $DIC;
1076
1077 $rbacreview = $DIC->rbac()->review();
1078
1079 $role_arr = $rbacreview->getRolesOfRoleFolder($a_ref_id);
1080 foreach ($role_arr as $role_id) {
1081 if (ilObject::_lookupTitle($role_id) === 'il_frm_moderator_' . $a_ref_id) {
1082 return array_map('intval', $rbacreview->assignedUsers($role_id));
1083 }
1084 }
1085
1086 return [];
1087 }
1088
1089 public static function _isModerator(int $a_ref_id, int $a_usr_id): bool
1090 {
1091 if (!isset(self::$moderators_by_ref_id_map[$a_ref_id])) {
1092 self::$moderators_by_ref_id_map[$a_ref_id] = self::_getModerators($a_ref_id);
1093 }
1094
1095 return in_array($a_usr_id, self::$moderators_by_ref_id_map[$a_ref_id], true);
1096 }
1097
1098 public function countUserArticles(int $a_user_id): int
1099 {
1100 $res = $this->db->queryF(
1101 'SELECT * FROM frm_data
1102 INNER JOIN frm_posts ON pos_top_fk = top_pk
1103 INNER JOIN frm_posts_tree tree1 ON tree1.pos_fk = frm_posts.pos_pk AND tree1.parent_pos != 0
1104 WHERE top_frm_fk = %s
1105 AND pos_author_id = %s',
1106 ['integer', 'integer'],
1107 [$this->getForumId(), $a_user_id]
1108 );
1109
1110 return $res->numRows();
1111 }
1112
1113 public function countActiveUserArticles(int $a_user_id): int
1114 {
1115 $res = $this->db->queryF(
1116 'SELECT * FROM frm_data
1117 INNER JOIN frm_posts ON pos_top_fk = top_pk
1118 INNER JOIN frm_posts_tree tree1 ON tree1.pos_fk = frm_posts.pos_pk AND tree1.parent_pos != 0
1119 WHERE top_frm_fk = %s
1120 AND (pos_status = %s OR (pos_status = %s AND pos_author_id = %s))
1121 AND pos_author_id = %s',
1122 ['integer', 'integer', 'integer', 'integer', 'integer'],
1123 [$this->getForumId(), 1, 0, $this->user->getId(), $a_user_id]
1124 );
1125
1126 return $res->numRows();
1127 }
1128
1129 public function convertDate(string $date): string
1130 {
1132 }
1133
1134 public function addPostTree(int $a_tree_id, int $a_node_id = -1, string $a_date = ''): bool
1135 {
1136 $a_date = $a_date ?: date('Y-m-d H:i:s');
1137
1138 if ($a_node_id <= 0) {
1139 $a_node_id = $a_tree_id;
1140 }
1141
1142 $nextId = $this->db->nextId('frm_posts_tree');
1143
1144 $this->db->manipulateF(
1145 '
1146 INSERT INTO frm_posts_tree
1147 ( fpt_pk,
1148 thr_fk,
1149 pos_fk,
1150 parent_pos,
1151 lft,
1152 rgt,
1153 depth,
1154 fpt_date
1155 )
1156 VALUES(%s, %s, %s, %s, %s, %s, %s, %s )',
1157 ['integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'timestamp'],
1158 [$nextId, $a_tree_id, $a_node_id, 0, 1, 2, 1, $a_date]
1159 );
1160
1161 return true;
1162 }
1163
1167 public function insertPostNode(int $a_node_id, int $a_parent_id, int $tree_id, string $a_date = ''): void
1168 {
1169 $a_date = $a_date ?: date('Y-m-d H:i:s');
1170 $left = 1;
1171
1172 $res = $this->db->queryF(
1173 'SELECT lft FROM frm_posts_tree WHERE pos_fk = %s AND thr_fk = %s',
1174 ['integer', 'integer'],
1175 [$a_parent_id, $tree_id]
1176 );
1177 $row = $this->db->fetchObject($res);
1178 if ($row instanceof stdClass) {
1179 $left = (int) $row->lft;
1180 }
1181
1182 $lft = $left + 1;
1183 $rgt = $left + 2;
1184
1185 $this->db->manipulateF(
1186 '
1187 UPDATE frm_posts_tree
1188 SET lft = CASE
1189 WHEN lft > %s
1190 THEN lft + 2
1191 ELSE lft
1192 END,
1193 rgt = CASE
1194 WHEN rgt > %s
1195 THEN rgt + 2
1196 ELSE rgt
1197 END
1198 WHERE thr_fk = %s',
1199 ['integer', 'integer', 'integer'],
1200 [$left, $left, $tree_id]
1201 );
1202
1203 $depth = $this->getPostDepth($a_parent_id, $tree_id) + 1;
1204
1205 $nextId = $this->db->nextId('frm_posts_tree');
1206 $this->db->manipulateF(
1207 '
1208 INSERT INTO frm_posts_tree
1209 ( fpt_pk,
1210 thr_fk,
1211 pos_fk,
1212 parent_pos,
1213 lft,
1214 rgt,
1215 depth,
1216 fpt_date
1217 )
1218 VALUES(%s,%s,%s, %s, %s, %s,%s, %s)',
1219 ['integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'integer', 'timestamp'],
1220 [
1221 $nextId,
1222 $tree_id,
1223 $a_node_id,
1224 $a_parent_id,
1225 $lft,
1226 $rgt,
1227 $depth,
1228 $a_date
1229 ]
1230 );
1231 }
1232
1233 public function getPostDepth(int $a_node_id, int $tree_id): int
1234 {
1235 if ($tree_id !== 0) {
1236 $res = $this->db->queryF(
1237 'SELECT depth FROM frm_posts_tree WHERE pos_fk = %s AND thr_fk = %s',
1238 ['integer', 'integer'],
1239 [$a_node_id, $tree_id]
1240 );
1241
1242 $row = $this->db->fetchObject($res);
1243 if ($row instanceof stdClass) {
1244 return (int) $row->depth;
1245 }
1246 }
1247
1248 return 0;
1249 }
1250
1251 public function getFirstPostNode(int $tree_id): array
1252 {
1253 $res = $this->db->queryF(
1254 'SELECT * FROM frm_posts, frm_posts_tree WHERE pos_pk = pos_fk AND parent_pos = %s AND thr_fk = %s',
1255 ['integer', 'integer'],
1256 [0, $tree_id]
1257 );
1258
1259 if (($row = $this->db->fetchObject($res)) !== null) {
1260 return $this->fetchPostNodeData($row);
1261 }
1262
1263 return [];
1264 }
1265
1266 public function getPostNode(int $post_id): array
1267 {
1268 $res = $this->db->queryF(
1269 'SELECT * FROM frm_posts, frm_posts_tree WHERE pos_pk = pos_fk AND pos_pk = %s',
1270 ['integer'],
1271 [$post_id]
1272 );
1273
1274 if (($row = $this->db->fetchObject($res)) !== null) {
1275 return $this->fetchPostNodeData($row);
1276 }
1277
1278 return [];
1279 }
1280
1284 public function fetchPostNodeData(stdClass $a_row): array
1285 {
1286 $fullname = '';
1287 $loginname = '';
1288
1289 if (ilObject::_exists((int) $a_row->pos_display_user_id)) {
1290 $tmp_user = new ilObjUser((int) $a_row->pos_display_user_id);
1291 $fullname = $tmp_user->getFullname();
1292 $loginname = $tmp_user->getLogin();
1293 }
1294
1295 if ($fullname === '') {
1296 $fullname = $this->lng->txt('unknown');
1297 if ($a_row->import_name) {
1298 $fullname = $a_row->import_name;
1299 }
1300 }
1301
1302 return [
1303 'type' => 'post',
1304 'pos_pk' => (int) $a_row->pos_pk,
1305 'pos_thr_fk' => (int) $a_row->pos_thr_fk,
1306 'child' => (int) $a_row->pos_pk,
1307 'author' => (int) $a_row->pos_display_user_id,
1308 'alias' => (string) $a_row->pos_usr_alias,
1309 'title' => $fullname,
1310 'loginname' => $loginname,
1311 'message' => (string) $a_row->pos_message,
1312 'subject' => (string) $a_row->pos_subject,
1313 'pos_cens_com' => (string) $a_row->pos_cens_com,
1314 'pos_cens' => (int) $a_row->pos_cens,
1315 'date' => $a_row->fpt_date,
1316 'create_date' => $a_row->pos_date,
1317 'update' => $a_row->pos_update,
1318 'update_user' => (int) $a_row->update_user,
1319 'tree' => (int) $a_row->thr_fk,
1320 'parent' => (int) $a_row->parent_pos,
1321 'lft' => (int) $a_row->lft,
1322 'rgt' => (int) $a_row->rgt,
1323 'depth' => (int) $a_row->depth,
1324 'id' => (int) $a_row->fpt_pk,
1325 'notify' => (int) $a_row->notify,
1326 'import_name' => $a_row->import_name,
1327 'pos_status' => (int) $a_row->pos_status
1328 ];
1329 }
1330
1335 {
1336 $res = $this->db->queryF(
1337 'SELECT pos_fk FROM frm_posts_tree WHERE lft BETWEEN %s AND %s AND thr_fk = %s',
1339 [$post->getLft(), $post->getRgt(), $post->getTreeId()]
1340 );
1341
1342 $post_ids = [];
1343 while ($post_tree_data = $this->db->fetchAssoc($res)) {
1344 $post_ids[] = (int) $post_tree_data['pos_fk'];
1345 }
1346
1347 return $post_ids;
1348 }
1349
1353 public function deletePostTree(array $a_node): array
1354 {
1355 $res = $this->db->queryF(
1356 'SELECT lft, rgt FROM frm_posts_tree WHERE thr_fk = %s AND pos_fk = %s AND parent_pos = %s',
1357 ['integer', 'integer', 'integer'],
1358 [$a_node['tree'], $a_node['pos_pk'], $a_node['parent']]
1359 );
1360
1361 while ($row = $this->db->fetchObject($res)) {
1362 $a_node['lft'] = (int) $row->lft;
1363 $a_node['rgt'] = (int) $row->rgt;
1364 }
1365
1366 $diff = $a_node['rgt'] - $a_node['lft'] + 1;
1367
1368 $res = $this->db->queryF(
1369 'SELECT pos_fk FROM frm_posts_tree WHERE lft BETWEEN %s AND %s AND thr_fk = %s',
1370 ['integer', 'integer', 'integer'],
1371 [$a_node['lft'], $a_node['rgt'], $a_node['tree']]
1372 );
1373
1374 $deleted_post_ids = [];
1375 while ($treeData = $this->db->fetchAssoc($res)) {
1376 $deleted_post_ids[] = (int) $treeData['pos_fk'];
1377 }
1378
1379 $this->db->manipulateF(
1380 'DELETE FROM frm_posts_tree WHERE lft BETWEEN %s AND %s AND thr_fk = %s',
1381 ['integer', 'integer', 'integer'],
1382 [$a_node['lft'], $a_node['rgt'], $a_node['tree']]
1383 );
1384
1385 $this->db->manipulateF(
1386 '
1387 UPDATE frm_posts_tree
1388 SET lft = CASE
1389 WHEN lft > %s
1390 THEN lft - %s
1391 ELSE lft
1392 END,
1393 rgt = CASE
1394 WHEN rgt > %s
1395 THEN rgt - %s
1396 ELSE rgt
1397 END
1398 WHERE thr_fk = %s',
1399 ['integer', 'integer', 'integer', 'integer', 'integer'],
1400 [$a_node['lft'], $diff, $a_node['lft'], $diff, $a_node['tree']]
1401 );
1402
1403 return $deleted_post_ids;
1404 }
1405
1406 public function updateVisits(int $ID): void
1407 {
1408 $checkTime = time() - (60 * 60);
1409 $session_key = 'frm_visit_' . $this->dbTable . '_' . $ID;
1410
1411 if (ilSession::get($session_key) < $checkTime) {
1412 ilSession::set($session_key, time());
1413 $query = 'UPDATE ' . $this->dbTable . ' SET visits = visits + 1 WHERE ';
1414 $data_type = [];
1415 $data_value = [];
1416
1417 if ($this->getMDB2Query() !== '' && $this->getMDB2DataType() !== [] && $this->getMDB2DataValue() !== []) {
1418 $query .= $this->getMDB2Query();
1419 $data_type += $this->getMDB2DataType();
1420 $data_value += $this->getMDB2DataValue();
1421
1422 $this->db->manipulateF($query, $data_type, $data_value);
1423 }
1424 }
1425 }
1426
1427 public function prepareText(string $text, int $edit = 0, string $quote_user = '', string $type = ''): string
1428 {
1429 if ($type === 'export') {
1430 $this->replQuote1 = "<blockquote class=\"quote\"><hr size=\"1\" color=\"#000000\">";
1431 $this->replQuote2 = "<hr size=\"1\" color=\"#000000\"/></blockquote>";
1432 }
1433
1434 if ($edit === 1) {
1435 $lname = '';
1436 if ($quote_user !== '') {
1437 $lname = '="' . $quote_user . '"';
1438 }
1439
1440 $text = "[quote$lname]" . $text . "[/quote]";
1441 } else {
1442 // check for quotation
1443 $startZ = substr_count($text, "[quote"); // also count [quote="..."]
1444 $endZ = substr_count($text, "[/quote]");
1445
1446 if ($startZ > 0 || $endZ > 0) {
1447 // add missing opening and closing tags
1448 if ($startZ > $endZ) {
1449 $diff = $startZ - $endZ;
1450
1451 for ($i = 0; $i < $diff; $i++) {
1452 if ($type === 'export') {
1453 $text .= $this->txtQuote2;
1454 } else {
1455 $text .= "[/quote]";
1456 }
1457 }
1458 } elseif ($startZ < $endZ) {
1459 $diff = $endZ - $startZ;
1460
1461 for ($i = 0; $i < $diff; $i++) {
1462 if ($type === 'export') {
1463 $text = $this->txtQuote1 . $text;
1464 } else {
1465 $text = "[quote]" . $text;
1466 }
1467 }
1468 }
1469
1470 if ($edit === 0) {
1471 $text = preg_replace(
1472 '@\[(quote\s*?=\s*?"([^"]*?)"\s*?)\]@i',
1473 $this->replQuote1 . '<div class="ilForumQuoteHead">' . $this->lng->txt('quote') . ' ($2)</div>',
1474 $text
1475 );
1476
1477 $text = str_replace(
1478 ["[quote]", "[/quote]"],
1479 [
1480 $this->replQuote1 . '<div class="ilForumQuoteHead">' . $this->lng->txt('quote') . '</div>',
1481 $this->replQuote2
1482 ],
1483 $text
1484 );
1485 }
1486 }
1487 }
1488
1489 if ($type !== 'export') {
1490 if ($edit === 0) {
1491 $text = ilRTE::replaceLatexSpan($text);
1492 $text = $this->ui_renderer->render($this->ui_factory->legacy()->latexContent($text));
1493 }
1494
1495 // workaround for preventing template engine
1496 // from hiding text that is enclosed
1497 // in curly brackets (e.g. "{a}")
1498 $text = str_replace(["{", "}"], ["&#123;", "&#125;"], $text);
1499 }
1500
1501 return $text;
1502 }
1503
1507 private function deletePostFiles(array $a_ids): void
1508 {
1509 foreach ($a_ids as $pos_id) {
1510 $forumFiles = new ilFileDataForum($this->getForumId(), $pos_id);
1511 $files = $forumFiles->getFilesOfPost();
1512 foreach ($files as $file) {
1513 $forumFiles->unlinkFile($file['name']);
1514 }
1515 }
1516 }
1517
1518 public function getImportName(): string
1519 {
1520 return $this->import_name;
1521 }
1522
1523 public function setImportName(string $a_import_name): void
1524 {
1525 $this->import_name = $a_import_name;
1526 }
1527
1528 public function enableForumNotification(int $user_id): bool
1529 {
1530 if (!$this->isForumNotificationEnabled($user_id)) {
1531 /* Remove all notifications of threads that belong to the forum */
1532 $res = $this->db->queryF(
1533 '
1534 SELECT frm_notification.thread_id FROM frm_data, frm_notification, frm_threads
1535 WHERE frm_notification.user_id = %s
1536 AND frm_notification.thread_id = frm_threads.thr_pk
1537 AND frm_threads.thr_top_fk = frm_data.top_pk
1538 AND frm_data.top_frm_fk = %s
1539 GROUP BY frm_notification.thread_id',
1540 ['integer', 'integer'],
1541 [$user_id, $this->id]
1542 );
1543
1544 if ($res->numRows() > 0) {
1545 $thread_data = [];
1546 $thread_data_types = [];
1547
1548 $query = ' DELETE FROM frm_notification WHERE user_id = %s AND thread_id IN (';
1549 $thread_data[] = $user_id;
1550 $thread_data_types[] = 'integer';
1551
1552 $counter = 1;
1553 while ($row = $this->db->fetchAssoc($res)) {
1554 if ($counter < $res->numRows()) {
1555 $query .= '%s, ';
1556 $thread_data[] = $row['thread_id'];
1557 $thread_data_types[] = 'integer';
1558 }
1559
1560 if ($counter === $res->numRows()) {
1561 $query .= '%s)';
1562 $thread_data[] = $row['thread_id'];
1563 $thread_data_types[] = 'integer';
1564 }
1565 $counter++;
1566 }
1567
1568 $this->db->manipulateF($query, $thread_data_types, $thread_data);
1569 }
1570
1571 /* Insert forum notification */
1572 $nextId = $this->db->nextId('frm_notification');
1573 $this->db->manipulateF(
1574 'INSERT INTO frm_notification (notification_id, user_id, frm_id) VALUES(%s, %s, %s)',
1575 ['integer', 'integer', 'integer'],
1576 [$nextId, $user_id, $this->id]
1577 );
1578 }
1579
1580 return true;
1581 }
1582
1583 public function disableForumNotification(int $user_id): bool
1584 {
1585 $this->db->manipulateF(
1586 'DELETE FROM frm_notification WHERE user_id = %s AND frm_id = %s',
1587 ['integer', 'integer'],
1588 [$user_id, $this->id]
1589 );
1590
1591 return true;
1592 }
1593
1594 public function isForumNotificationEnabled(int $user_id): bool
1595 {
1596 $res = $this->db->queryF(
1597 'SELECT COUNT(*) cnt FROM frm_notification WHERE user_id = %s AND frm_id = %s',
1598 ['integer', 'integer'],
1599 [$user_id, $this->id]
1600 );
1601
1602 if ($row = $this->db->fetchAssoc($res)) {
1603 return (int) $row['cnt'] > 0;
1604 }
1605
1606 return false;
1607 }
1608
1609 public function isThreadNotificationEnabled(int $user_id, int $thread_id): bool
1610 {
1611 $res = $this->db->queryF(
1612 'SELECT COUNT(*) cnt FROM frm_notification WHERE user_id = %s AND thread_id = %s',
1613 ['integer', 'integer'],
1614 [$user_id, $thread_id]
1615 );
1616
1617 if ($row = $this->db->fetchAssoc($res)) {
1618 return (int) $row['cnt'] > 0;
1619 }
1620
1621 return false;
1622 }
1623
1627 public static function getSortedThreadSubjects(int $a_obj_id, int $a_sort_mode = self::SORT_DATE): array
1628 {
1629 global $DIC;
1630
1631 $sort = match ($a_sort_mode) {
1632 self::SORT_DATE => 'thr_date',
1633 default => 'thr_subject',
1634 };
1635
1636 $res = $DIC->database()->queryF(
1637 'SELECT thr_pk, thr_subject FROM frm_threads INNER JOIN frm_data ON top_pk = thr_top_fk WHERE top_frm_fk = %s ORDER BY %s',
1638 ['integer', 'text'],
1639 [$a_obj_id, $sort]
1640 );
1641
1642 $threads = [];
1643 while ($row = $DIC->database()->fetchObject($res)) {
1644 $threads[(int) $row->thr_pk] = $row->thr_subject;
1645 }
1646
1647 return $threads;
1648 }
1649
1650 public static function _lookupObjIdForForumId(int $a_for_id): int
1651 {
1652 global $DIC;
1653
1654 $res = $DIC->database()->queryF('SELECT top_frm_fk FROM frm_data WHERE top_pk = %s', ['integer'], [$a_for_id]);
1655 if ($row = $DIC->database()->fetchAssoc($res)) {
1656 return (int) $row['top_frm_fk'];
1657 }
1658
1659 return 0;
1660 }
1661
1662 public function mergeThreads(int $source_id, int $target_id): void
1663 {
1664 // selected source and target objects
1665 $sourceThread = new ilForumTopic($source_id);
1666 $targetThread = new ilForumTopic($target_id);
1667
1668 if ($sourceThread->getForumId() !== $targetThread->getForumId()) {
1669 throw new ilException('not_allowed_to_merge_into_another_forum');
1670 }
1671
1672 // use the 'older' thread as target
1673 if ($sourceThread->getCreateDate() > $targetThread->getCreateDate()) {
1674 $sourceThreadForMerge = $sourceThread;
1675 $targetThreadForMerge = $targetThread;
1676 } else {
1677 $sourceThreadForMerge = $targetThread;
1678 $targetThreadForMerge = $sourceThread;
1679 }
1680
1681 $threadSubject = $targetThreadForMerge->getSubject();
1682
1683 $targetWasClosedBeforeMerge = $targetThreadForMerge->isClosed();
1684 $sourceThreadForMerge->close();
1685
1686 if (!$targetWasClosedBeforeMerge) {
1687 $targetThreadForMerge->close();
1688 }
1689
1690 $allSourcePostings = $sourceThreadForMerge->getAllPostIds();
1691 $sourceThreadRootNode = $sourceThreadForMerge->getPostRootNode();
1692 $targetThreadRootNode = $targetThreadForMerge->getPostRootNode();
1693
1694 $sourceThreadRootArray = $this->getPostNode($sourceThreadRootNode->getId());
1695
1696 $ilAtomQuery = $this->db->buildAtomQuery();
1697 $ilAtomQuery->addTableLock('frm_posts');
1698 $ilAtomQuery->addTableLock('frm_posts_tree');
1699 $ilAtomQuery->addTableLock('frm_threads');
1700 $ilAtomQuery->addTableLock('frm_data');
1701
1702 $ilAtomQuery->addQueryCallable(static function (ilDBInterface $ilDB) use (
1703 $targetThreadForMerge,
1704 $sourceThreadForMerge,
1705 $targetThreadRootNode,
1706 $sourceThreadRootNode,
1707 $allSourcePostings
1708 ): void {
1709 $targetRootNodeRgt = $targetThreadRootNode->getRgt();
1710 $targetRootNodeId = $targetThreadRootNode->getId();
1711
1712 // update target root node rgt: Ignore the root node itself from the source (= -2)
1714 $targetThreadRootNode->getId(),
1715 ($targetThreadRootNode->getRgt() + $sourceThreadRootNode->getRgt() - 2)
1716 );
1717
1718 // get source post tree and update posts tree
1719 foreach ($allSourcePostings as $pos_pk) {
1720 $post_obj = new ilForumPost($pos_pk);
1721
1722 if ($post_obj->getId() === $sourceThreadRootNode->getId()) {
1723 // Ignore the source root node (MUST be deleted later)
1724 continue;
1725 }
1726
1727 $tree = new ilForumPostsTree();
1728 $tree->setPosFk($pos_pk);
1729
1730 if ($post_obj->getParentId() === $sourceThreadRootNode->getId()) {
1731 $tree->setParentPos($targetRootNodeId);
1732 } else {
1733 $tree->setParentPos($post_obj->getParentId());
1734 }
1735
1736 $tree->setLft(($post_obj->getLft() + $targetRootNodeRgt) - 2);
1737 $tree->setRgt(($post_obj->getRgt() + $targetRootNodeRgt) - 2);
1738
1739 $tree->setDepth($post_obj->getDepth());
1740 $tree->setTargetThreadId($targetThreadForMerge->getId());
1741 $tree->setSourceThreadId($sourceThreadForMerge->getId());
1742
1743 $tree->merge();
1744 }
1745
1747 $sourceThreadForMerge->getId(),
1748 $targetThreadForMerge->getId(),
1749 [$sourceThreadRootNode->getId()]
1750 );
1751 });
1752 $ilAtomQuery->run();
1753
1754 ilForumNotification::mergeThreadNotifications($sourceThreadForMerge->getId(), $targetThreadForMerge->getId());
1755 ilObjForum::mergeForumUserRead($sourceThreadForMerge->getId(), $targetThreadForMerge->getId());
1756
1757 $lastPostString = $targetThreadForMerge->getLastPostString() ?? '';
1758 $exp = explode('#', $lastPostString);
1759 if (array_key_exists(2, $exp)) {
1760 try {
1761 $exp[2] = $targetThreadForMerge->getLastPost()->getId();
1762 $lastPostString = implode('#', $exp);
1763 } catch (OutOfBoundsException) {
1764 $lastPostString = null;
1765 }
1766 }
1767
1768 $frm_topic_obj = new ilForumTopic(0, false, true);
1769 $frm_topic_obj->setNumPosts($sourceThreadForMerge->getNumPosts() + $targetThreadForMerge->getNumPosts());
1770 $frm_topic_obj->setVisits($sourceThreadForMerge->getVisits() + $targetThreadForMerge->getVisits());
1771 $frm_topic_obj->setLastPostString($lastPostString);
1772 $frm_topic_obj->setSubject($threadSubject);
1773 $frm_topic_obj->setId($targetThreadForMerge->getId());
1774 $frm_topic_obj->updateMergedThread();
1775
1776 if (!$targetWasClosedBeforeMerge) {
1777 $targetThreadForMerge->reopen();
1778 }
1779
1780 $this->event->raise(
1781 'components/ILIAS/Forum',
1782 'mergedThreads',
1783 [
1784 'obj_id' => $this->getForumId(),
1785 'source_thread_id' => $sourceThreadForMerge->getId(),
1786 'target_thread_id' => $targetThreadForMerge->getId()
1787 ]
1788 );
1789
1790 $this->deletePost($sourceThreadRootArray, false);
1791 }
1792}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static getEmptyInstance()
static getInstanceFromArray(array $record)
Builds a Color from either hex- or rgb values.
Definition: Factory.php:31
Personal profile publishing mode of a user.
Definition: Visibility.php:30
const IL_CAL_DATETIME
error(string $a_errmsg)
const NEWS_USERS
const NEWS_NOTICE
Global event handler.
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
@classDescription Date and time handling
Error Handling & global info handling.
Base class for ILIAS Exception handling.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Class ilForumDraftHistory.
static mergeThreadNotifications($merge_source_thread_id, $merge_target_thread_id)
Class ilForumPostDraft.
setForumId(int $a_forum_id)
static mergePosts(int $sourceThreadId, int $targetThreadId, array $excludedPostIds=[])
static updateTargetRootRgt(int $root_node_id, int $rgt)
static getInstance(int $a_obj_id=0)
setCreateDate(?string $a_createdate)
Class Forum core functions for forum.
deletePostFiles(array $a_ids)
static _lookupObjIdForForumId(int $a_for_id)
getRootPostIdByThread(int $a_thread_id)
isForumNotificationEnabled(int $user_id)
static getSortedThreadSubjects(int $a_obj_id, int $a_sort_mode=self::SORT_DATE)
string $import_name
prepareText(string $text, int $edit=0, string $quote_user='', string $type='')
ILIAS UI Factory $ui_factory
setImportName(string $a_import_name)
postCensorship(ilObjForum $forum, string $message, int $pos_pk, int $cens=0)
updateVisits(int $ID)
readonly ilAppEventHandler $event
mergeThreads(int $source_id, int $target_id)
getAllThreads(int $a_topic_id, array $params=[], int $limit=0, int $offset=0)
static _getModerators(int $a_ref_id)
setMDB2WhereCondition(string $query_string, array $data_type, array $data_value)
getPostDepth(int $a_node_id, int $tree_id)
setPageHits(int $pageHits)
addPostTree(int $a_tree_id, int $a_node_id=-1, string $a_date='')
const int SORT_DATE
getUserStatistics(bool $post_activation_required)
getNumberOfPublishedUserPostings(int $usr_id, bool $post_activation_required)
fetchPostNodeData(stdClass $a_row)
string $dbTable
string $mdb2Query
const int DEFAULT_PAGE_HITS
disableForumNotification(int $user_id)
ilErrorHandling $error
deletePostTree(array $a_node)
countActiveUserArticles(int $a_user_id)
getPostNode(int $post_id)
string $txtQuote1
const int SORT_TITLE
ilSetting $settings
string $txtQuote2
ilObjUser $user
int $pageHits
ilLanguage $lng
insertPostNode(int $a_node_id, int $a_parent_id, int $tree_id, string $a_date='')
insert node under parent node
setForumRefId(int $a_ref_id)
static array $moderators_by_ref_id_map
deletePost($postIdOrRecord, bool $raiseEvents=true)
convertDate(string $date)
string $className
array $mdb2DataType
string $replQuote1
isThreadNotificationEnabled(int $user_id, int $thread_id)
getFirstPostNode(int $tree_id)
ILIAS UI Renderer $ui_renderer
setForumId(int $a_obj_id)
generateThread(ilForumTopic $thread, string $message, bool $notify, bool $notify_posts, bool $status=true, bool $withFirstVisibleEntry=true)
enableForumNotification(int $user_id)
countUserArticles(int $a_user_id)
static _isModerator(int $a_ref_id, int $a_usr_id)
moveThreads(array $thread_ids, ilObjForum $src_forum, int $target_obj_id)
string $replQuote2
getMDB2DataValue()
array $mdb2DataValue
setDbTable(string $dbTable)
ilDBInterface $db
getSubPathIdsForNode(ilForumPost $post)
language handling
A news item can be created by different sources.
static getFirstNewsIdForContext(int $a_context_obj_id, string $a_context_obj_type, int $a_context_sub_obj_id=0, string $a_context_sub_obj_type="")
Get first new id of news set related to a certain context.
Class ilObjForum.
static _deleteReadEntries(int $a_post_id)
static mergeForumUserRead(int $merge_source_thread_id, int $merge_target_thread_id)
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
static _getMobsOfObject(string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
static _removeUsage(int $a_mob_id, string $a_type, int $a_id, int $a_usage_hist_nr=0, string $a_lang="-")
Remove usage of mob in another container.
User class.
static getInstanceByRefId(int $ref_id, bool $stop_on_error=true)
get an instance of an Ilias object by reference id
static _exists(int $id, bool $reference=false, ?string $type=null)
checks if an object exists in object_data
static _lookupTitle(int $obj_id)
static replaceLatexSpan(string $text)
Replace the latex delimiters used by the rich text editor Unfortunately these can't be processed by M...
static _replaceMediaObjectImageSrc(string $a_text, int $a_direction=0, string $nic='')
Replaces image source from mob image urls with the mob id or replaces mob id with the correct image s...
static get(string $a_var)
static set(string $a_var, $a_val)
Set a value.
ILIAS Setting Class.
An entity that renders components to a string output.
Definition: Renderer.php:31
Interface ilDBInterface.
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:31
$res
Definition: ltiservices.php:69
$post
Definition: ltitoken.php:46
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26
$counter
bool $notify
$message
Definition: xapiexit.php:31