ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilObjForum.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
26 class ilObjForum extends ilObject
27 {
28  public const NEWS_NEW_CONSIDERATION_WEEKS = 8;
29 
30  public ilForum $Forum;
32  private static array $obj_id_to_forum_id_cache = [];
34  private static array $ref_id_to_forum_id_cache = [];
36  private static array $forum_statistics_cache = [];
38  private static array $forum_last_post_cache = [];
39  private \ILIAS\DI\RBACServices $rbac;
40  private ilLogger $logger;
41 
42  public function __construct(int $a_id = 0, bool $a_call_by_reference = true)
43  {
44  global $DIC;
45 
46  $this->type = 'frm';
47  parent::__construct($a_id, $a_call_by_reference);
48 
49  $this->rbac = $DIC->rbac();
50  $this->logger = $DIC->logger()->root();
51 
52  $settings = $DIC->settings();
53  $weeks = self::NEWS_NEW_CONSIDERATION_WEEKS;
54  if ($settings->get('frm_store_new')) {
55  $weeks = (int) $settings->get('frm_store_new');
56  }
57  $new_deadline = time() - 60 * 60 * 24 * 7 * $weeks;
58  $settings->set('frm_new_deadline', (string) $new_deadline);
59 
60  $this->Forum = new ilForum();
61  }
62 
63  public function create(): int
64  {
65  $id = parent::create();
66 
67  $properties = ilForumProperties::getInstance($this->getId());
68  $properties->setDefaultView(ilForumProperties::VIEW_DATE_ASC);
69  $properties->setAnonymisation(false);
70  $properties->setStatisticsStatus(false);
71  $properties->setPostActivation(false);
72  $properties->setThreadSorting(0);
73  $properties->insert();
74 
75  $this->createSettings();
76 
77  $this->setOfflineStatus(true);
78  $this->update();
79  $this->saveData();
80 
81  return $id;
82  }
83 
84  public function setPermissions(int $parent_ref_id): void
85  {
86  parent::setPermissions($parent_ref_id);
87 
88  $roles = [self::_lookupModeratorRole($this->getRefId())];
89  $this->rbac->admin()->assignUser($roles[0], $this->getOwner());
90  $this->updateModeratorRole($roles[0]);
91  }
92 
93  public function updateModeratorRole(int $role_id): void
94  {
95  $this->db->manipulate('UPDATE frm_data SET top_mods = ' . $this->db->quote(
96  $role_id,
97  'integer'
98  ) . ' WHERE top_frm_fk = ' . $this->db->quote($this->getId(), 'integer'));
99  }
100 
101  public static function _lookupThreadSubject(int $a_thread_id): string
102  {
103  global $DIC;
104 
105  $ilDB = $DIC->database();
106 
107  $res = $ilDB->queryF('SELECT thr_subject FROM frm_threads WHERE thr_pk = %s', ['integer'], [$a_thread_id]);
108  while ($row = $ilDB->fetchObject($res)) {
109  return $row->thr_subject ?? '';
110  }
111 
112  return '';
113  }
114 
115  public function getCountUnread(int $a_usr_id, int $a_thread_id = 0, bool $ignoreRoot = false): int
116  {
117  $a_frm_id = $this->getId();
118  $topic_id = 0;
119  $num_posts = 0;
120  $count_read = 0;
121 
122  if ($a_thread_id === 0) {
123  $res = $this->db->queryF('SELECT top_pk FROM frm_data WHERE top_frm_fk = %s', ['integer'], [$a_frm_id]);
124  while ($row = $this->db->fetchObject($res)) {
125  $topic_id = (int) $row->top_pk;
126  }
127 
128  $res = $this->db->queryF(
129  '
130  SELECT COUNT(pos_pk) num_posts
131  FROM frm_posts
132  LEFT JOIN frm_posts_tree ON frm_posts_tree.pos_fk = pos_pk
133  WHERE pos_top_fk = %s' . ($ignoreRoot ? ' AND parent_pos != 0 ' : ''),
134  ['integer'],
135  [$topic_id]
136  );
137 
138  while ($row = $this->db->fetchObject($res)) {
139  $num_posts = (int) $row->num_posts;
140  }
141 
142  $res = $this->db->queryF(
143  'SELECT COUNT(post_id) count_read FROM frm_user_read WHERE obj_id = %s AND usr_id = %s',
144  ['integer', 'integer'],
145  [$a_frm_id, $a_usr_id]
146  );
147 
148  while ($row = $this->db->fetchObject($res)) {
149  $count_read = (int) $row->count_read;
150  }
151  } else {
152  $res = $this->db->queryF(
153  '
154  SELECT COUNT(pos_pk) num_posts FROM frm_posts
155  LEFT JOIN frm_posts_tree ON frm_posts_tree.pos_fk = pos_pk
156  WHERE pos_thr_fk = %s' . ($ignoreRoot ? ' AND parent_pos != 0 ' : ''),
157  ['integer'],
158  [$a_thread_id]
159  );
160 
161  $row = $this->db->fetchObject($res);
162  $num_posts = (int) $row->num_posts;
163 
164  $res = $this->db->queryF(
165  '
166  SELECT COUNT(post_id) count_read FROM frm_user_read
167  WHERE obj_id = %s
168  AND usr_id = %s
169  AND thread_id = %s',
170  ['integer', 'integer', 'integer'],
171  [$a_frm_id, $a_frm_id, $a_thread_id]
172  );
173 
174  $row = $this->db->fetchObject($res);
175  $count_read = (int) $row->count_read;
176  }
177  $unread = $num_posts - $count_read;
178 
179  return max($unread, 0);
180  }
181 
182  public function markThreadRead(int $a_usr_id, int $a_thread_id): bool
183  {
184  $res = $this->db->queryF('SELECT pos_pk FROM frm_posts WHERE pos_thr_fk = %s', ['integer'], [$a_thread_id]);
185  while ($row = $this->db->fetchObject($res)) {
186  $this->markPostRead($a_usr_id, $a_thread_id, (int) $row->pos_pk);
187  }
188 
189  return true;
190  }
191 
192  public function markAllThreadsRead(int $a_usr_id): void
193  {
194  $res = $this->db->queryF(
195  'SELECT thr_pk FROM frm_data, frm_threads WHERE top_frm_fk = %s AND top_pk = thr_top_fk',
196  ['integer'],
197  [$this->getId()]
198  );
199 
200  while ($row = $this->db->fetchObject($res)) {
201  $this->markThreadRead($a_usr_id, (int) $row->thr_pk);
202  }
203  }
204 
205  public function markPostRead(int $a_usr_id, int $a_thread_id, int $a_post_id): void
206  {
207  $res = $this->db->queryF(
208  '
209  SELECT thread_id FROM frm_user_read
210  WHERE usr_id = %s
211  AND obj_id = %s
212  AND thread_id = %s
213  AND post_id = %s',
214  ['integer', 'integer', 'integer', 'integer'],
215  [$a_usr_id, $this->getId(), $a_thread_id, $a_post_id]
216  );
217 
218  if (!$this->db->numRows($res)) {
219  $this->db->manipulateF(
220  '
221  INSERT INTO frm_user_read
222  ( usr_id,
223  obj_id,
224  thread_id,
225  post_id
226  )
227  VALUES (%s,%s,%s,%s)',
228  ['integer', 'integer', 'integer', 'integer'],
229  [$a_usr_id, $this->getId(), $a_thread_id, $a_post_id]
230  );
231  }
232  }
233 
234  public function markPostUnread(int $a_user_id, int $a_post_id): void
235  {
236  $this->db->manipulateF(
237  'DELETE FROM frm_user_read WHERE usr_id = %s AND post_id = %s',
238  ['integer', 'integer'],
239  [$a_user_id, $a_post_id]
240  );
241  }
242 
243  public function isRead($a_usr_id, $a_post_id): bool
244  {
245  $res = $this->db->queryF(
246  'SELECT * FROM frm_user_read WHERE usr_id = %s AND post_id = %s',
247  ['integer', 'integer'],
248  [$a_usr_id, $a_post_id]
249  );
250 
251  return (bool) $this->db->numRows($res);
252  }
253 
254  public function updateLastAccess(int $a_usr_id, int $a_thread_id): void
255  {
256  $res = $this->db->queryF(
257  'SELECT * FROM frm_thread_access WHERE usr_id = %s AND obj_id = %s AND thread_id = %s',
258  ['integer', 'integer', 'integer'],
259  [$a_usr_id, $this->getId(), $a_thread_id]
260  );
261  $data = $this->db->fetchAssoc($res);
262 
263  if (is_array($data)) {
264  $this->db->replace(
265  'frm_thread_access',
266  [
267  'usr_id' => ['integer', $a_usr_id],
268  'obj_id' => ['integer', $this->getId()],
269  'thread_id' => ['integer', $a_thread_id]
270  ],
271  [
272  'access_last' => ['integer', time()],
273  'access_old' => ['integer', (int) ($data['access_old'] ?? 0)],
274  'access_old_ts' => ['timestamp', $data['access_old_ts']]
275  ]
276  );
277  }
278  }
279 
280  public static function _updateOldAccess(int $a_usr_id): void
281  {
282  global $DIC;
283 
284  $ilDB = $DIC->database();
285 
286  $ilDB->manipulateF(
287  'UPDATE frm_thread_access SET access_old = access_last WHERE usr_id = %s',
288  ['integer'],
289  [$a_usr_id]
290  );
291 
292  $res = $ilDB->query(
293  'SELECT * FROM frm_thread_access WHERE usr_id = ' . $ilDB->quote($a_usr_id, 'integer')
294  );
295  while ($row = $ilDB->fetchAssoc($res)) {
296  $ilDB->manipulate(
297  "UPDATE frm_thread_access SET " .
298  " access_old_ts = " . $ilDB->quote(date('Y-m-d H:i:s', (int) $row["access_old"]), "timestamp") .
299  " WHERE usr_id = " . $ilDB->quote((int) $row["usr_id"], "integer") .
300  " AND obj_id = " . $ilDB->quote((int) $row["obj_id"], "integer") .
301  " AND thread_id = " . $ilDB->quote((int) $row["thread_id"], "integer")
302  );
303  }
304 
305  $weeks = self::NEWS_NEW_CONSIDERATION_WEEKS;
306  if ($DIC->settings()->get('frm_store_new')) {
307  $weeks = (int) $DIC->settings()->get('frm_store_new');
308  }
309  $new_deadline = time() - 60 * 60 * 24 * 7 * $weeks;
310 
311  $ilDB->manipulateF('DELETE FROM frm_thread_access WHERE access_last < %s', ['integer'], [$new_deadline]);
312  }
313 
314  public static function _deleteUser(int $a_usr_id): void
315  {
316  global $DIC;
317 
318  $data = [$a_usr_id];
319 
320  $DIC->database()->manipulateF('DELETE FROM frm_user_read WHERE usr_id = %s', ['integer'], $data);
321  $DIC->database()->manipulateF('DELETE FROM frm_thread_access WHERE usr_id = %s', ['integer'], $data);
322  $DIC->database()->manipulateF('DELETE FROM frm_notification WHERE user_id = %s', ['integer'], $data);
323  }
324 
325  public static function _deleteReadEntries(int $a_post_id): void
326  {
327  global $DIC;
328 
329  $DIC->database()->manipulateF('DELETE FROM frm_user_read WHERE post_id = %s', ['integer'], [$a_post_id]);
330  }
331 
332  public static function _deleteAccessEntries(int $a_thread_id): void
333  {
334  global $DIC;
335 
336  $DIC->database()->manipulateF('DELETE FROM frm_thread_access WHERE thread_id = %s', ['integer'], [$a_thread_id]);
337  }
338 
339  public function updateMoficationUserId(int $usr_id): void
340  {
341  $this->db->manipulateF(
342  'UPDATE frm_data SET update_user = %s WHERE top_frm_fk = %s',
343  ['integer', 'integer'],
344  [$usr_id, $this->getId()],
345  );
346  }
347 
348  public function update(): bool
349  {
350  if (parent::update()) {
351  $this->db->manipulateF(
352  'UPDATE frm_data SET top_name = %s, top_description = %s, top_update = %s, update_user = %s WHERE top_frm_fk = %s',
353  ['text', 'text', 'timestamp', 'integer', 'integer'],
354  [
355  $this->getTitle(),
356  $this->getDescription(),
357  date("Y-m-d H:i:s"),
358  $this->user->getId(),
359  $this->getId()
360  ]
361  );
362 
363  return true;
364  }
365 
366  return false;
367  }
368 
369  public function cloneObject(int $target_id, int $copy_id = 0, bool $omit_tree = false): ?ilObject
370  {
372  $new_obj = parent::cloneObject($target_id, $copy_id, $omit_tree);
373  $this->cloneAutoGeneratedRoles($new_obj);
374 
375  ilForumProperties::getInstance($this->getId())->copy($new_obj->getId());
376  $this->Forum->setMDB2WhereCondition('top_frm_fk = %s ', ['integer'], [$this->getId()]);
377  $topData = $this->Forum->getOneTopic();
378 
379  $this->db->update('frm_data', [
380  'top_name' => ['text', $topData->getTopName()],
381  'top_description' => ['text', $topData->getTopDescription()],
382  'top_num_posts' => ['integer', $topData->getTopNumPosts()],
383  'top_num_threads' => ['integer', $topData->getTopNumThreads()],
384  'top_last_post' => ['text', $topData->getTopLastPost()],
385  'top_date' => ['timestamp', $topData->getTopDate()],
386  'visits' => ['integer', $topData->getVisits()],
387  'top_update' => ['timestamp', $topData->getTopUpdate()],
388  'update_user' => ['integer', $topData->getUpdateUser()],
389  'top_usr_id' => ['integer', $topData->getTopUsrId()]
390  ], [
391  'top_frm_fk' => ['integer', $new_obj->getId()]
392  ]);
393 
394  $cwo = ilCopyWizardOptions::_getInstance($copy_id);
395  $options = $cwo->getOptions($this->getRefId());
396 
397  $options['threads'] = $this->Forum::getSortedThreadSubjects($this->getId());
398 
399  $new_frm = $new_obj->Forum;
400  $new_frm->setMDB2WhereCondition('top_frm_fk = %s ', ['integer'], [$new_obj->getId()]);
401 
402  $new_frm->setForumId($new_obj->getId());
403  $new_frm->setForumRefId($new_obj->getRefId());
404 
405  $new_topic = $new_frm->getOneTopic();
406  foreach ($options['threads'] as $thread_id => $thread_subject) {
407  $this->Forum->setMDB2WhereCondition('thr_pk = %s ', ['integer'], [$thread_id]);
408 
409  $old_thread = $this->Forum->getOneThread();
410 
411  $old_post_id = $this->Forum->getRootPostIdByThread($old_thread->getId());
412 
413  $newThread = new ilForumTopic(0, true, true);
414  $newThread->setSticky($old_thread->isSticky());
415  $newThread->setForumId($new_topic->getTopPk());
416  $newThread->setThrAuthorId($old_thread->getThrAuthorId());
417  $newThread->setDisplayUserId($old_thread->getDisplayUserId());
418  $newThread->setSubject($old_thread->getSubject());
419  $newThread->setUserAlias($old_thread->getUserAlias());
420  $newThread->setCreateDate($old_thread->getCreateDate());
421 
422  try {
423  $top_pos = $old_thread->getFirstVisiblePostNode();
424  } catch (OutOfBoundsException $e) {
425  $top_pos = new ilForumPost($old_post_id);
426  }
427 
428  $newPostId = $new_frm->generateThread(
429  $newThread,
430  $top_pos->getMessage(),
431  $top_pos->isNotificationEnabled(),
432  false,
433  true,
434  (bool) ($old_thread->getNumPosts() - 1)
435  );
436 
437  $old_forum_files = new ilFileDataForum($this->getId(), $top_pos->getId());
438  $old_forum_files->ilClone($new_obj->getId(), $newPostId);
439  }
440 
441  $sourceRefId = $this->getRefId();
442  $targetRefId = $new_obj->getRefId();
443 
444  if (
445  $sourceRefId > 0 && $targetRefId > 0 &&
446  $this->tree->getParentId($sourceRefId) === $this->tree->getParentId($targetRefId)
447  ) {
448  $grpRefId = $this->tree->checkForParentType($targetRefId, 'grp');
449  $crsRefId = $this->tree->checkForParentType($targetRefId, 'crs');
450 
451  if ($grpRefId > 0 || $crsRefId > 0) {
452  $notifications = new ilForumNotification($targetRefId);
453  $notifications->cloneFromSource($sourceRefId);
454  }
455  }
456 
457  if (ilForumPage::_exists($this->getType(), $this->getId())) {
458  $translations = ilContentPagePage::lookupTranslations($this->getType(), $this->getId());
459  foreach ($translations as $language) {
460  $originalPageObject = new ilForumPage($this->getId(), 0, $language);
461  $copiedXML = $originalPageObject->copyXmlContent();
462 
463  $duplicatePageObject = new ilForumPage();
464  $duplicatePageObject->setId($new_obj->getId());
465  $duplicatePageObject->setParentId($new_obj->getId());
466  $duplicatePageObject->setLanguage($language);
467  $duplicatePageObject->setXMLContent($copiedXML);
468  $duplicatePageObject->createFromXML();
469  }
470  }
471 
472  $cwo = ilCopyWizardOptions::_getInstance($copy_id);
473  //copy online status if object is not the root copy object
474  if (!$cwo->isRootNode($this->getRefId())) {
475  $new_obj->setOfflineStatus($this->getOfflineStatus());
476  } else {
477  $new_obj->setOfflineStatus(true);
478  }
479  $new_obj->update();
480 
481  return $new_obj;
482  }
483 
484  public function cloneAutoGeneratedRoles(self $new_obj): void
485  {
486  $src_moderator_role_id = self::_lookupModeratorRole($this->getRefId());
487  $new_moderator_role_id = self::_lookupModeratorRole($new_obj->getRefId());
488 
489  if (
490  0 === $src_moderator_role_id ||
491  0 === $new_moderator_role_id ||
492  0 === $this->getRefId() ||
493  0 === $new_obj->getRefId()
494  ) {
495  $this->logger->write(__METHOD__ . ' : Error cloning auto generated role: il_frm_moderator');
496  }
497 
498  $this->rbac->admin()->copyRolePermissions(
499  $src_moderator_role_id,
500  $this->getRefId(),
501  $new_obj->getRefId(),
502  $new_moderator_role_id,
503  true
504  );
505 
506  $this->logger->write(__METHOD__ . ' : Finished copying of role il_frm_moderator.');
507 
508  $moderators = new ilForumModerators($this->getRefId());
509  $src_moderator_usr_ids = $moderators->getCurrentModerators();
510  foreach ($src_moderator_usr_ids as $usr_id) {
511  // The object owner is already member of the moderator role when this method is called
512  // Since the new static caches are introduced with ILIAS 5.0, a database error occurs if we try to assign the user here.
513  if ($this->getOwner() !== $usr_id) {
514  $this->rbac->admin()->assignUser($new_moderator_role_id, $usr_id);
515  }
516  }
517  }
518 
519  public function delete(): bool
520  {
521  $this->Forum->setForumId($this->getId());
522 
523  if (!parent::delete()) {
524  return false;
525  }
526 
527  if (ilForumPage::_exists($this->getType(), $this->getId())) {
528  $originalPageObject = new ilForumPage($this->getId());
529  $originalPageObject->delete();
530  }
531 
532  $tmp_file_obj = new ilFileDataForum($this->getId());
533  $tmp_file_obj->delete();
534 
535  $this->Forum->setMDB2WhereCondition('top_frm_fk = %s ', ['integer'], [$this->getId()]);
536 
537  $topData = $this->Forum->getOneTopic();
538 
539  $threads = $this->Forum->getAllThreads($topData->getTopPk(), [
540  'is_moderator' => true,
541  ]);
542  $thread_ids_to_delete = [];
543  foreach ($threads['items'] as $thread) {
544  $thread_ids_to_delete[$thread->getId()] = $thread->getId();
545  }
546 
547  $this->db->manipulate('DELETE FROM frm_posts_tree WHERE ' . $this->db->in(
548  'thr_fk',
549  $thread_ids_to_delete,
550  false,
551  'integer'
552  ));
553  $this->db->manipulate('DELETE FROM frm_posts WHERE ' . $this->db->in(
554  'pos_thr_fk',
555  $thread_ids_to_delete,
556  false,
557  'integer'
558  ));
559  $this->db->manipulate('DELETE FROM frm_threads WHERE ' . $this->db->in(
560  'thr_pk',
561  $thread_ids_to_delete,
562  false,
563  'integer'
564  ));
565 
566  $obj_id = [$this->getId()];
567 
568  $this->db->manipulateF('DELETE FROM frm_data WHERE top_frm_fk = %s', ['integer'], $obj_id);
569  $this->db->manipulateF('DELETE FROM frm_settings WHERE obj_id = %s', ['integer'], $obj_id);
570  $this->db->manipulateF('DELETE FROM frm_user_read WHERE obj_id = %s', ['integer'], $obj_id);
571  $this->db->manipulateF('DELETE FROM frm_thread_access WHERE obj_id = %s', ['integer'], $obj_id);
572  $this->db->manipulate('DELETE FROM frm_notification WHERE ' . $this->db->in(
573  'thread_id',
574  $thread_ids_to_delete,
575  false,
576  'integer'
577  ));
578  $this->db->manipulateF('DELETE FROM frm_notification WHERE frm_id = %s', ['integer'], $obj_id);
579  $this->db->manipulateF('DELETE FROM frm_posts_deleted WHERE obj_id = %s', ['integer'], $obj_id);
580  $this->deleteDraftsByForumId($topData->getTopPk());
581 
582  return true;
583  }
584 
585  private function deleteDraftsByForumId(int $forum_id): void
586  {
587  $res = $this->db->queryF(
588  'SELECT draft_id FROM frm_posts_drafts WHERE forum_id = %s',
589  ['integer'],
590  [$forum_id]
591  );
592 
593  $draft_ids = [];
594  while ($row = $this->db->fetchAssoc($res)) {
595  $draft_ids[] = (int) $row['draft_id'];
596  }
597 
598  if (count($draft_ids) > 0) {
599  $historyObj = new ilForumDraftsHistory();
600  $historyObj->deleteHistoryByDraftIds($draft_ids);
601 
602  $draftObj = new ilForumPostDraft();
603  $draftObj->deleteDraftsByDraftIds($draft_ids);
604  }
605  }
606 
607  public function initDefaultRoles(): void
608  {
610  'il_frm_moderator_' . $this->getRefId(),
611  "Moderator of forum obj_no." . $this->getId(),
612  'il_frm_moderator',
613  $this->getRefId()
614  );
615  }
616 
617  public static function _lookupModeratorRole(int $a_ref_id): int
618  {
619  global $DIC;
620 
621  $ilDB = $DIC->database();
622 
623  $mod_title = 'il_frm_moderator_' . $a_ref_id;
624 
625  $res = $ilDB->queryF('SELECT obj_id FROM object_data WHERE title = %s', ['text'], [$mod_title]);
626  while ($row = $ilDB->fetchObject($res)) {
627  return (int) $row->obj_id;
628  }
629 
630  return 0;
631  }
632 
633  public function createSettings(): void
634  {
635  global $DIC;
636 
637  $ref_id = 0;
638  if ($DIC->http()->wrapper()->query()->has('ref_id')) {
639  $ref_id = $DIC->http()->wrapper()->query()->retrieve(
640  'ref_id',
641  $DIC->refinery()->kindlyTo()->int()
642  );
643  }
644 
645  // news settings (public notifications yes/no)
646  $default_visibility = ilNewsItem::_getDefaultVisibilityForRefId($ref_id);
647  if ($default_visibility === 'public') {
648  ilBlockSetting::_write('news', 'public_notifications', '1', 0, $this->getId());
649  }
650  }
651 
652  public function saveData(): void
653  {
654  $nextId = $this->db->nextId('frm_data');
655 
656  $top_data = [
657  'top_frm_fk' => $this->getId(),
658  'top_name' => $this->getTitle(),
659  'top_description' => $this->getDescription(),
660  'top_num_posts' => 0,
661  'top_num_threads' => 0,
662  'top_last_post' => null,
663  'top_mods' => 0,
664  'top_usr_id' => $this->user->getId(),
665  'top_date' => ilUtil::now()
666  ];
667 
668  $this->db->manipulateF(
669  '
670  INSERT INTO frm_data
671  (
672  top_pk,
673  top_frm_fk,
674  top_name,
675  top_description,
676  top_num_posts,
677  top_num_threads,
678  top_last_post,
679  top_mods,
680  top_date,
681  top_usr_id
682  )
683  VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
684  [
685  'integer',
686  'integer',
687  'text',
688  'text',
689  'integer',
690  'integer',
691  'text',
692  'integer',
693  'timestamp',
694  'integer'
695  ],
696  [
697  $nextId,
698  $top_data['top_frm_fk'],
699  $top_data['top_name'],
700  $top_data['top_description'],
701  $top_data['top_num_posts'],
702  $top_data['top_num_threads'],
703  $top_data['top_last_post'],
704  $top_data['top_mods'],
705  $top_data['top_date'],
706  $top_data['top_usr_id']
707  ]
708  );
709  }
710 
711  public function setThreadSorting(int $a_thr_pk, int $a_sorting_value): void
712  {
713  $this->db->update(
714  'frm_threads',
715  ['thread_sorting' => ['integer', $a_sorting_value]],
716  ['thr_pk' => ['integer', $a_thr_pk]]
717  );
718  }
719 
720  public static function lookupForumIdByObjId(int $obj_id): int
721  {
722  if (array_key_exists($obj_id, self::$obj_id_to_forum_id_cache)) {
723  return self::$obj_id_to_forum_id_cache[$obj_id];
724  }
725 
726  self::preloadForumIdsByObjIds([$obj_id]);
727 
728  return self::$obj_id_to_forum_id_cache[$obj_id];
729  }
730 
731  public static function lookupForumIdByRefId(int $ref_id): int
732  {
733  if (array_key_exists($ref_id, self::$ref_id_to_forum_id_cache)) {
734  return self::$ref_id_to_forum_id_cache[$ref_id];
735  }
736 
737  self::preloadForumIdsByRefIds([$ref_id]);
738 
739  return self::$ref_id_to_forum_id_cache[$ref_id];
740  }
741 
745  public static function preloadForumIdsByObjIds(array $obj_ids): void
746  {
747  global $DIC;
748 
749  $ilDB = $DIC->database();
750 
751  if (count($obj_ids) === 1) {
752  $in = ' objr.obj_id = ' . $ilDB->quote(current($obj_ids), 'integer') . ' ';
753  } else {
754  $in = $ilDB->in('objr.obj_id', $obj_ids, false, 'integer');
755  }
756  $query = "
757  SELECT frmd.top_pk, objr.ref_id, objr.obj_id
758  FROM object_reference objr
759  INNER JOIN frm_data frmd ON frmd.top_frm_fk = objr.obj_id
760  WHERE $in
761  ";
762  $res = $ilDB->query($query);
763 
764  // Prepare cache array
765  foreach ($obj_ids as $obj_id) {
766  self::$obj_id_to_forum_id_cache[$obj_id] = 0;
767  }
768 
769  while ($row = $ilDB->fetchAssoc($res)) {
770  self::$obj_id_to_forum_id_cache[(int) $row['obj_id']] = (int) $row['top_pk'];
771  self::$ref_id_to_forum_id_cache[(int) $row['ref_id']] = (int) $row['top_pk'];
772  }
773  }
774 
778  public static function preloadForumIdsByRefIds(array $ref_ids): void
779  {
780  global $DIC;
781 
782  $ilDB = $DIC->database();
783 
784  if (count($ref_ids) === 1) {
785  $in = " objr.ref_id = " . $ilDB->quote(current($ref_ids), 'integer') . " ";
786  } else {
787  $in = $ilDB->in('objr.ref_id', $ref_ids, false, 'integer');
788  }
789  $query = "
790  SELECT frmd.top_pk, objr.ref_id, objr.obj_id
791  FROM object_reference objr
792  INNER JOIN frm_data frmd ON frmd.top_frm_fk = objr.obj_id
793  WHERE $in
794  ";
795  $res = $ilDB->query($query);
796 
797  // Prepare cache array
798  foreach ($ref_ids as $ref_id) {
799  if (!array_key_exists($ref_id, self::$ref_id_to_forum_id_cache)) {
800  self::$ref_id_to_forum_id_cache[$ref_id] = 0;
801  }
802  }
803 
804  while ($row = $ilDB->fetchAssoc($res)) {
805  self::$obj_id_to_forum_id_cache[(int) $row['obj_id']] = (int) $row['top_pk'];
806  self::$ref_id_to_forum_id_cache[(int) $row['ref_id']] = (int) $row['top_pk'];
807  }
808  }
809 
814  public static function lookupStatisticsByRefId(int $ref_id): array
815  {
816  global $DIC;
817 
818  $ilAccess = $DIC->access();
819  $ilUser = $DIC->user();
820  $ilDB = $DIC->database();
821  $ilSetting = $DIC->settings();
822 
823  if (isset(self::$forum_statistics_cache[$ref_id])) {
824  return self::$forum_statistics_cache[$ref_id];
825  }
826 
827  $statistics = [
828  'num_posts' => 0,
829  'num_unread_posts' => 0,
830  'num_new_posts' => 0
831  ];
832 
833  $forumId = self::lookupForumIdByRefId($ref_id);
834  if (!$forumId) {
835  self::$forum_statistics_cache[$ref_id] = $statistics;
836  return self::$forum_statistics_cache[$ref_id];
837  }
838 
840  $is_post_activation_enabled = $objProperties->isPostActivationEnabled();
841 
842  $act_clause = '';
843 
844  if ($is_post_activation_enabled && !$ilAccess->checkAccess('moderate_frm', '', $ref_id)) {
845  $act_clause .= ' AND (frm_posts.pos_status = ' . $ilDB->quote(
846  1,
847  'integer'
848  ) . ' OR frm_posts.pos_author_id = ' . $ilDB->quote($ilUser->getId(), 'integer') . ') ';
849  }
850 
851  $weeks = self::NEWS_NEW_CONSIDERATION_WEEKS;
852  if ($ilSetting->get('frm_store_new')) {
853  $weeks = (int) $ilSetting->get('frm_store_new');
854  }
855 
856  $new_deadline = (new DateTime())
857  ->setTimestamp(time() - 60 * 60 * 24 * 7 * $weeks)
858  ->format('Y-m-d H:i:s');
859 
860  if (!$ilUser->isAnonymous()) {
861  $query = "
862  (SELECT COUNT(frm_posts.pos_pk) cnt
863  FROM frm_posts
864  INNER JOIN frm_posts_tree tree1
865  ON tree1.pos_fk = frm_posts.pos_pk
866  AND tree1.parent_pos != 0
867  INNER JOIN frm_threads ON frm_posts.pos_thr_fk = frm_threads.thr_pk
868  WHERE frm_threads.thr_top_fk = %s $act_clause)
869 
870  UNION ALL
871 
872  (SELECT COUNT(DISTINCT(frm_user_read.post_id)) cnt
873  FROM frm_user_read
874  INNER JOIN frm_posts ON frm_user_read.post_id = frm_posts.pos_pk
875  INNER JOIN frm_posts_tree tree1
876  ON tree1.pos_fk = frm_posts.pos_pk
877  AND tree1.parent_pos != 0
878  INNER JOIN frm_threads ON frm_threads.thr_pk = frm_posts.pos_thr_fk
879  WHERE frm_user_read.usr_id = %s AND frm_posts.pos_top_fk = %s $act_clause)
880  ";
881 
882  $types = ['integer', 'integer', 'integer'];
883  $values = [$forumId, $ilUser->getId(), $forumId];
884 
885  $forum_overview_setting = (int) ilSetting::_lookupValue('frma', 'forum_overview');
886  if ($forum_overview_setting === ilForumProperties::FORUM_OVERVIEW_WITH_NEW_POSTS) {
887  $news_types = ['integer', 'integer', 'integer', 'timestamp', 'integer'];
888  $news_values = [$ilUser->getId(), $ilUser->getId(), $forumId, $new_deadline, $ilUser->getId()];
889 
890  $query .= "
891  UNION ALL
892 
893  (SELECT COUNT(frm_posts.pos_pk) cnt
894  FROM frm_posts
895  INNER JOIN frm_posts_tree tree1
896  ON tree1.pos_fk = frm_posts.pos_pk
897  AND tree1.parent_pos != 0
898  LEFT JOIN frm_user_read ON (post_id = frm_posts.pos_pk AND frm_user_read.usr_id = %s)
899  LEFT JOIN frm_thread_access ON (frm_thread_access.thread_id = frm_posts.pos_thr_fk AND frm_thread_access.usr_id = %s)
900  WHERE frm_posts.pos_top_fk = %s
901  AND ( (frm_posts.pos_update > frm_thread_access.access_old_ts)
902  OR (frm_thread_access.access_old IS NULL AND frm_posts.pos_update > %s)
903  )
904  AND frm_posts.pos_author_id != %s
905  AND frm_user_read.usr_id IS NULL $act_clause)";
906 
907  $types = array_merge($types, $news_types);
908  $values = array_merge($values, $news_values);
909  }
910 
911  $mapping = array_keys($statistics);
912  $res = $ilDB->queryF(
913  $query,
914  $types,
915  $values
916  );
917  for ($i = 0; $i <= 2; $i++) {
918  $row = $ilDB->fetchAssoc($res);
919 
920  $statistics[$mapping[$i]] = (int) ((is_array($row) ? $row['cnt'] : 0));
921 
922  if ($i === 1) {
923  // unread = all - read
924  $statistics[$mapping[$i]] = $statistics[$mapping[$i - 1]] - $statistics[$mapping[$i]];
925  }
926  }
927  } else {
928  $query = "
929  SELECT COUNT(frm_posts.pos_pk) cnt
930  FROM frm_posts
931  INNER JOIN frm_posts_tree tree1
932  ON tree1.pos_fk = frm_posts.pos_pk
933  AND tree1.parent_pos != 0
934  INNER JOIN frm_threads ON frm_posts.pos_thr_fk = frm_threads.thr_pk
935  WHERE frm_threads.thr_top_fk = %s $act_clause
936  ";
937  $types = ['integer'];
938  $values = [$forumId];
939  $res = $ilDB->queryF(
940  $query,
941  $types,
942  $values
943  );
944  $row = $ilDB->fetchAssoc($res);
945 
946  $statistics = [
947  'num_posts' => (int) $row['cnt'],
948  'num_unread_posts' => (int) $row['cnt'],
949  'num_new_posts' => (int) $row['cnt']
950  ];
951  }
952 
953  self::$forum_statistics_cache[$ref_id] = $statistics;
954 
955  return self::$forum_statistics_cache[$ref_id];
956  }
957 
958  public static function lookupLastPostByRefId(int $ref_id): ?array
959  {
960  global $DIC;
961 
962  $ilAccess = $DIC->access();
963  $ilUser = $DIC->user();
964  $ilDB = $DIC->database();
965 
966  if (array_key_exists($ref_id, self::$forum_last_post_cache)) {
967  return self::$forum_last_post_cache[$ref_id];
968  }
969 
970  $forumId = self::lookupForumIdByRefId($ref_id);
971  if (!$forumId) {
972  self::$forum_last_post_cache[$ref_id] = null;
973  return self::$forum_last_post_cache[$ref_id];
974  }
975 
976  $act_clause = '';
977  if (!$ilAccess->checkAccess('moderate_frm', '', $ref_id)) {
978  $act_clause .= ' AND (frm_posts.pos_status = ' . $ilDB->quote(
979  1,
980  'integer'
981  ) . ' OR frm_posts.pos_author_id = ' . $ilDB->quote($ilUser->getId(), 'integer') . ') ';
982  }
983 
984  $ilDB->setLimit(1, 0);
985  $query = "
986  SELECT *
987  FROM frm_posts
988  INNER JOIN frm_posts_tree tree1
989  ON tree1.pos_fk = frm_posts.pos_pk
990  AND tree1.parent_pos != 0
991  WHERE pos_top_fk = %s $act_clause
992  ORDER BY pos_date DESC
993  ";
994  $res = $ilDB->queryF(
995  $query,
996  ['integer'],
997  [$forumId]
998  );
999 
1000  $data = $ilDB->fetchAssoc($res);
1001  if (!is_array($data) || empty($data)) {
1002  self::$forum_last_post_cache[$ref_id] = null;
1003  return self::$forum_last_post_cache[$ref_id];
1004  }
1005 
1006  $casted_data = [];
1007  $casted_data['pos_pk'] = (int) $data['pos_pk'];
1008  $casted_data['pos_top_fk'] = (int) $data['pos_top_fk'];
1009  $casted_data['pos_thr_fk'] = (int) $data['pos_thr_fk'];
1010  $casted_data['pos_usr_alias'] = (string) $data['pos_usr_alias'];
1011  $casted_data['pos_subject'] = (string) $data['pos_subject'];
1012  $casted_data['pos_date'] = (string) $data['pos_date'];
1013  $casted_data['pos_update'] = (string) $data['pos_update'];
1014  $casted_data['update_user'] = (int) $data['update_user'];
1015  $casted_data['pos_cens'] = (int) $data['pos_cens'];
1016  $casted_data['pos_cens_com'] = (string) $data['pos_cens_com'];
1017  $casted_data['notify'] = (int) $data['notify'];
1018  $casted_data['import_name'] = (string) $data['import_name'];
1019  $casted_data['pos_status'] = (int) $data['pos_status'];
1020  $casted_data['pos_message'] = (string) $data['pos_message'];
1021  $casted_data['pos_author_id'] = (int) $data['pos_author_id'];
1022  $casted_data['pos_display_user_id'] = (int) $data['pos_display_user_id'];
1023  $casted_data['is_author_moderator'] = (int) $data['is_author_moderator'];
1024  $casted_data['pos_cens_date'] = (string) $data['pos_cens_date'];
1025  $casted_data['pos_activation_date'] = (string) $data['pos_activation_date'];
1026 
1027  self::$forum_last_post_cache[$ref_id] = $casted_data;
1028 
1029  return self::$forum_last_post_cache[$ref_id];
1030  }
1031 
1037  public static function getUserIdsOfLastPostsByRefIdAndThreadIds(int $ref_id, array $thread_ids): array
1038  {
1039  global $DIC;
1040 
1041  $ilAccess = $DIC->access();
1042  $ilUser = $DIC->user();
1043  $ilDB = $DIC->database();
1044 
1045  $act_clause = '';
1046  $act_inner_clause = '';
1047  if (!$ilAccess->checkAccess('moderate_frm', '', $ref_id)) {
1048  $act_clause .= " AND (t1.pos_status = " . $ilDB->quote(
1049  1,
1050  "integer"
1051  ) . " OR t1.pos_author_id = " . $ilDB->quote($ilUser->getId(), "integer") . ") ";
1052  $act_inner_clause .= " AND (t3.pos_status = " . $ilDB->quote(
1053  1,
1054  "integer"
1055  ) . " OR t3.pos_author_id = " . $ilDB->quote($ilUser->getId(), "integer") . ") ";
1056  }
1057 
1058  $in = $ilDB->in("t1.pos_thr_fk", $thread_ids, false, 'integer');
1059  $inner_in = $ilDB->in("t3.pos_thr_fk", $thread_ids, false, 'integer');
1060 
1061  $query = "
1062  SELECT t1.pos_display_user_id, t1.update_user
1063  FROM frm_posts t1
1064  INNER JOIN frm_posts_tree tree1 ON tree1.pos_fk = t1.pos_pk AND tree1.parent_pos != 0
1065  INNER JOIN (
1066  SELECT t3.pos_thr_fk, MAX(t3.pos_date) pos_date
1067  FROM frm_posts t3
1068  INNER JOIN frm_posts_tree tree2 ON tree2.pos_fk = t3.pos_pk AND tree2.parent_pos != 0
1069  WHERE $inner_in $act_inner_clause
1070  GROUP BY t3.pos_thr_fk
1071  ) t2 ON t2.pos_thr_fk = t1.pos_thr_fk AND t2.pos_date = t1.pos_date
1072  WHERE $in $act_clause
1073  GROUP BY t1.pos_thr_fk, t1.pos_display_user_id, t1.update_user
1074  ";
1075 
1076  $usr_ids = [];
1077 
1078  $res = $ilDB->query($query);
1079  while ($row = $ilDB->fetchAssoc($res)) {
1080  if ((int) $row['pos_display_user_id']) {
1081  $usr_ids[] = (int) $row['pos_display_user_id'];
1082  }
1083  if ((int) $row['update_user']) {
1084  $usr_ids[] = (int) $row['update_user'];
1085  }
1086  }
1087 
1088  return array_unique($usr_ids);
1089  }
1090 
1091  public static function mergeForumUserRead(int $merge_source_thread_id, int $merge_target_thread_id): void
1092  {
1093  global $DIC;
1094 
1095  $DIC->database()->update(
1096  'frm_user_read',
1097  ['thread_id' => ['integer', $merge_target_thread_id]],
1098  ['thread_id' => ['integer', $merge_source_thread_id]]
1099  );
1100  }
1101 
1102  public function getNumStickyThreads(): int
1103  {
1104  $res = $this->db->query(
1105  'SELECT COUNT(is_sticky) num_sticky FROM frm_threads
1106  INNER JOIN frm_data ON top_pk = thr_top_fk
1107  WHERE frm_data.top_frm_fk = ' . $this->db->quote($this->getId(), 'integer') . '
1108  AND is_sticky = ' . $this->db->quote(1, 'integer')
1109  );
1110  if ($row = $this->db->fetchAssoc($res)) {
1111  return (int) $row['num_sticky'];
1112  }
1113 
1114  return 0;
1115  }
1116 
1120  public function getPageObjIds(): array
1121  {
1122  $pageObjIds = [];
1123 
1124  $sql = 'SELECT DISTINCT page_id FROM page_object WHERE parent_id = %s AND parent_type = %s';
1125  $res = $this->db->queryF(
1126  $sql,
1127  ['integer', 'text'],
1128  [$this->getId(), $this->getType()]
1129  );
1130 
1131  while ($row = $this->db->fetchAssoc($res)) {
1132  $pageObjIds[] = (int) $row['page_id'];
1133  }
1134 
1135  return $pageObjIds;
1136  }
1137 }
Class ilForumPostDraft.
__construct(int $a_id=0, bool $a_call_by_reference=true)
$res
Definition: ltiservices.php:69
static mergeForumUserRead(int $merge_source_thread_id, int $merge_target_thread_id)
array $settings
Setting values (LTI parameters, custom parameters and local parameters).
Definition: System.php:200
Class Forum core functions for forum.
static _lookupThreadSubject(int $a_thread_id)
static array $forum_statistics_cache
static _getDefaultVisibilityForRefId(int $a_ref_id)
Get default visibility for reference id.
static lookupForumIdByObjId(int $obj_id)
const NEWS_NEW_CONSIDERATION_WEEKS
static _lookupModeratorRole(int $a_ref_id)
updateModeratorRole(int $role_id)
static _deleteAccessEntries(int $a_thread_id)
static getUserIdsOfLastPostsByRefIdAndThreadIds(int $ref_id, array $thread_ids)
ILIAS DI RBACServices $rbac
deleteDraftsByForumId(int $forum_id)
$target_id
Definition: goto.php:52
static createDefaultRole(string $a_title, string $a_description, string $a_tpl_name, int $a_ref_id)
static preloadForumIdsByObjIds(array $obj_ids)
static lookupTranslations(string $a_parent_type, int $a_id)
Lookup translations.
static lookupForumIdByRefId(int $ref_id)
static now()
Return current timestamp in Y-m-d H:i:s format.
markAllThreadsRead(int $a_usr_id)
static _deleteReadEntries(int $a_post_id)
Class ilForumDraftHistory.
static getInstance(int $a_obj_id=0)
static _lookupValue(string $a_module, string $a_keyword)
static _deleteUser(int $a_usr_id)
global $DIC
Definition: feed.php:28
updateMoficationUserId(int $usr_id)
static lookupStatisticsByRefId(int $ref_id)
static _updateOldAccess(int $a_usr_id)
static _exists(string $a_parent_type, int $a_id, string $a_lang="", bool $a_no_cache=false)
Checks whether page exists.
static _write(string $a_type, string $a_setting, string $a_value, int $a_user=0, int $a_block_id=0)
Write setting to database.
static _lookupObjectId(int $ref_id)
setThreadSorting(int $a_thr_pk, int $a_sorting_value)
$query
static array $obj_id_to_forum_id_cache
setOfflineStatus(bool $status)
static array $forum_last_post_cache
updateLastAccess(int $a_usr_id, int $a_thread_id)
ilLogger $logger
getCountUnread(int $a_usr_id, int $a_thread_id=0, bool $ignoreRoot=false)
static array $ref_id_to_forum_id_cache
static lookupLastPostByRefId(int $ref_id)
isRead($a_usr_id, $a_post_id)
global $ilSetting
Definition: privfeed.php:17
__construct(Container $dic, ilPlugin $plugin)
This class handles all operations on files for the forum object.
$ilUser
Definition: imgupload.php:34
static _getInstance(int $a_copy_id)
markPostUnread(int $a_user_id, int $a_post_id)
setPermissions(int $parent_ref_id)
Class ilObjForum.
Class ilForumModerators.
markThreadRead(int $a_usr_id, int $a_thread_id)
static preloadForumIdsByRefIds(array $ref_ids)
markPostRead(int $a_usr_id, int $a_thread_id, int $a_post_id)
$i
Definition: metadata.php:41
cloneAutoGeneratedRoles(self $new_obj)
ilClone(int $a_new_obj_id, int $a_new_pos_id)