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