ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilForumCronNotification.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
11 {
13 
17  protected $settings;
18 
22  protected $logger;
23 
27  protected $tree;
28 
32  public static $providerObject = array();
33 
37  protected static $deleted_ids_cache = array();
38 
42  protected static $ref_ids_by_obj_id = array();
43 
47  protected static $accessible_ref_ids_by_user = array();
48 
50  private static $container_by_frm_ref_id = [];
51 
55  protected $num_sent_messages = 0;
56 
58  private $ilDB;
59 
62 
67  public function __construct(\ilDBInterface $database = null, \ilForumNotificationCache $notificationCache = null)
68  {
69  $this->settings = new ilSetting('frma');
70 
71  global $DIC;
72  if ($database === null) {
73  $ilDB = $DIC->database();
74  }
75  $this->ilDB = $ilDB;
76 
77  if ($notificationCache === null) {
78  $notificationCache = new \ilForumNotificationCache();
79  }
80  $this->notificationCache = $notificationCache;
81  }
82 
83  public function getId()
84  {
85  return "frm_notification";
86  }
87 
88  public function getTitle()
89  {
90  global $DIC;
91 
92  return $DIC->language()->txt("cron_forum_notification");
93  }
94 
95  public function getDescription()
96  {
97  global $DIC;
98 
99  return $DIC->language()->txt("cron_forum_notification_crob_desc");
100  }
101 
102  public function getDefaultScheduleType()
103  {
104  return self::SCHEDULE_TYPE_IN_HOURS;
105  }
106 
107  public function getDefaultScheduleValue()
108  {
109  return 1;
110  }
111 
112  public function hasAutoActivation()
113  {
114  return false;
115  }
116 
117  public function hasFlexibleSchedule()
118  {
119  return true;
120  }
121 
125  public function hasCustomSettings()
126  {
127  return true;
128  }
129 
133  public function keepAlive()
134  {
135  $this->logger->debug('Sending ping to cron manager ...');
136  \ilCronManager::ping($this->getId());
137  $this->logger->debug(sprintf('Current memory usage: %s', memory_get_usage(true)));
138  }
139 
143  public function run()
144  {
145  global $DIC;
146 
147  $ilSetting = $DIC->settings();
148  $lng = $DIC->language();
149  $this->tree = $DIC->repositoryTree();
150 
151  $this->logger = $DIC->logger()->frm();
152 
154 
155  $lng->loadLanguageModule('forum');
156 
157  $this->logger->info('Started forum notification job ...');
158 
159  if (!($last_run_datetime = $ilSetting->get('cron_forum_notification_last_date'))) {
160  $last_run_datetime = null;
161  }
162 
163  $this->num_sent_messages = 0;
164  $cj_start_date = date('Y-m-d H:i:s');
165 
166  if ($last_run_datetime != null &&
167  checkDate(date('m', strtotime($last_run_datetime)), date('d', strtotime($last_run_datetime)), date('Y', strtotime($last_run_datetime)))) {
168  $threshold = max(strtotime($last_run_datetime), strtotime('-' . (int) $this->settings->get('max_notification_age', 30) . ' days', time()));
169  } else {
170  $threshold = strtotime('-' . (int) $this->settings->get('max_notification_age', 30) . ' days', time());
171  }
172 
173  $this->logger->info(sprintf('Threshold for forum event determination is: %s', date('Y-m-d H:i:s', $threshold)));
174 
175  $threshold_date = date('Y-m-d H:i:s', $threshold);
176 
177  $this->sendNotificationForNewPosts($threshold_date);
178 
179  $this->sendNotificationForUpdatedPosts($threshold_date);
180 
181  $this->sendNotificationForCensoredPosts($threshold_date);
182 
183  $this->sendNotificationForUncensoredPosts($threshold_date);
184 
186 
188 
189  $ilSetting->set('cron_forum_notification_last_date', $cj_start_date);
190 
191  $mess = 'Sent ' . $this->num_sent_messages . ' messages.';
192 
193  $this->logger->info($mess);
194  $this->logger->info('Finished forum notification job');
195 
196  $result = new ilCronJobResult();
197  if ($this->num_sent_messages) {
198  $status = ilCronJobResult::STATUS_OK;
199  $result->setMessage($mess);
200  };
201  $result->setStatus($status);
202  return $result;
203  }
204 
209  protected function getRefIdsByObjId($a_obj_id)
210  {
211  if (!array_key_exists($a_obj_id, self::$ref_ids_by_obj_id)) {
212  self::$ref_ids_by_obj_id[$a_obj_id] = ilObject::_getAllReferences($a_obj_id);
213  }
214 
215  return (array) self::$ref_ids_by_obj_id[$a_obj_id];
216  }
217 
223  protected function getFirstAccessibleRefIdBUserAndObjId($a_user_id, $a_obj_id)
224  {
225  global $DIC;
226  $ilAccess = $DIC->access();
227 
228  if (!array_key_exists($a_user_id, self::$accessible_ref_ids_by_user)) {
229  self::$accessible_ref_ids_by_user[$a_user_id] = array();
230  }
231 
232  if (!array_key_exists($a_obj_id, self::$accessible_ref_ids_by_user[$a_user_id])) {
233  $accessible_ref_id = 0;
234  foreach ($this->getRefIdsByObjId($a_obj_id) as $ref_id) {
235  if ($ilAccess->checkAccessOfUser($a_user_id, 'read', '', $ref_id)) {
236  $accessible_ref_id = $ref_id;
237  break;
238  }
239  }
240  self::$accessible_ref_ids_by_user[$a_user_id][$a_obj_id] = $accessible_ref_id;
241  }
242 
243  return (int) self::$accessible_ref_ids_by_user[$a_user_id][$a_obj_id];
244  }
245 
250  public function sendCronForumNotification($res, $notification_type)
251  {
252  global $DIC;
253  $ilDB = $DIC->database();
254 
255  while ($row = $ilDB->fetchAssoc($res)) {
256  if ($notification_type == ilForumMailNotification::TYPE_POST_DELETED
257  || $notification_type == ilForumMailNotification::TYPE_THREAD_DELETED) {
258  // important! save the deleted_id to cache before proceeding getFirstAccessibleRefIdBUserAndObjId !
259  self::$deleted_ids_cache[$row['deleted_id']] = $row['deleted_id'];
260  }
261 
262  $ref_id = $this->getFirstAccessibleRefIdBUserAndObjId($row['user_id'], $row['obj_id']);
263  if ($ref_id < 1) {
264  $this->logger->debug(sprintf(
265  'The recipient with id %s has no "read" permission for object with id %s',
266  $row['user_id'],
267  $row['obj_id']
268  ));
269  continue;
270  }
271 
272  $row['ref_id'] = $ref_id;
273 
274  $container = $this->determineClosestContainer($ref_id);
275  if ($container instanceof ilObjCourse || $container instanceof ilObjGroup) {
276  $row['closest_container'] = $container;
277  }
278 
279  if ($this->existsProviderObject($row['pos_pk'])) {
280  self::$providerObject[$row['pos_pk']]->addRecipient($row['user_id']);
281  } else {
282  $this->addProviderObject($row);
283  }
284  }
285 
286  $usrIdsToPreload = array();
287  foreach (self::$providerObject as $provider) {
288  if ($provider->getPosAuthorId()) {
289  $usrIdsToPreload[$provider->getPosAuthorId()] = $provider->getPosAuthorId();
290  }
291  if ($provider->getPosDisplayUserId()) {
292  $usrIdsToPreload[$provider->getPosDisplayUserId()] = $provider->getPosDisplayUserId();
293  }
294  if ($provider->getPostUpdateUserId()) {
295  $usrIdsToPreload[$provider->getPostUpdateUserId()] = $provider->getPostUpdateUserId();
296  }
297  }
298 
299  ilForumAuthorInformationCache::preloadUserObjects(array_unique($usrIdsToPreload));
300 
301  $i = 0;
302  foreach (self::$providerObject as $provider) {
303  if ($i > 0 && ($i % self::KEEP_ALIVE_CHUNK_SIZE) == 0) {
304  $this->keepAlive();
305  }
306 
307  $recipients = array_unique($provider->getCronRecipients());
308 
309  $this->logger->info(sprintf(
310  'Trying to send forum notifications for posting id "%s", type "%s" and recipients: %s',
311  $provider->getPostId(),
312  $notification_type,
313  implode(', ', $recipients)
314  ));
315 
316  $mailNotification = new ilForumMailNotification($provider, $this->logger);
317  $mailNotification->setIsCronjob(true);
318  $mailNotification->setType($notification_type);
319  $mailNotification->setRecipients($recipients);
320 
321  $mailNotification->send();
322 
323  $this->num_sent_messages += count($provider->getCronRecipients());
324  $this->logger->info(sprintf("Sent notifications ... "));
325 
326  ++$i;
327  }
328 
329  $this->resetProviderCache();
330  }
331 
336  public function determineClosestContainer(int $frm_ref_id) : ?ilObject
337  {
338  if (isset(self::$container_by_frm_ref_id[$frm_ref_id])) {
339  return self::$container_by_frm_ref_id[$frm_ref_id];
340  }
341 
342  $ref_id = $this->tree->checkForParentType($frm_ref_id, 'crs');
343  if (!($ref_id > 0)) {
344  $ref_id = $this->tree->checkForParentType($frm_ref_id, 'grp');
345  }
346 
347  if ($ref_id > 0) {
350  self::$container_by_frm_ref_id[$frm_ref_id] = $container;
351  return $container;
352  }
353 
354  return null;
355  }
356 
361  public function existsProviderObject($post_id)
362  {
363  if (isset(self::$providerObject[$post_id])) {
364  return true;
365  }
366  return false;
367  }
368 
372  private function addProviderObject($row)
373  {
374  $tmp_provider = new ilForumCronNotificationDataProvider($row, $this->notificationCache);
375 
376  self::$providerObject[$row['pos_pk']] = $tmp_provider;
377  self::$providerObject[$row['pos_pk']]->addRecipient($row['user_id']);
378  }
379 
383  private function resetProviderCache()
384  {
385  self::$providerObject = array();
386  }
387 
393  public function addToExternalSettingsForm($a_form_id, array &$a_fields, $a_is_active)
394  {
395  global $DIC;
396  $lng = $DIC->language();
397 
398  switch ($a_form_id) {
400  $a_fields['cron_forum_notification'] = $a_is_active ?
401  $lng->txt('enabled') :
402  $lng->txt('disabled');
403  break;
404  }
405  }
406 
410  public function activationWasToggled($a_currently_active)
411  {
412  global $DIC;
413 
414  $value = 1;
415  // propagate cron-job setting to object setting
416  if ((bool) $a_currently_active) {
417  $value = 2;
418  }
419  $DIC->settings()->set('forum_notification', $value);
420  }
421 
426  {
427  global $DIC;
428  $lng = $DIC->language();
429 
430  $lng->loadLanguageModule('forum');
431 
432  $max_notification_age = new ilNumberInputGUI($lng->txt('frm_max_notification_age'), 'max_notification_age');
433  $max_notification_age->setSize(5);
434  $max_notification_age->setSuffix($lng->txt('frm_max_notification_age_unit'));
435  $max_notification_age->setRequired(true);
436  $max_notification_age->allowDecimals(false);
437  $max_notification_age->setMinValue(1);
438  $max_notification_age->setInfo($lng->txt('frm_max_notification_age_info'));
439  $max_notification_age->setValue($this->settings->get('max_notification_age', 30));
440 
441  $a_form->addItem($max_notification_age);
442  }
443 
448  public function saveCustomSettings(ilPropertyFormGUI $a_form)
449  {
450  $this->settings->set('max_notification_age', $a_form->getInput('max_notification_age'));
451  return true;
452  }
453 
457  private function sendNotificationForNewPosts(string $threshold_date)
458  {
459  $condition = '
460  frm_posts.pos_status = %s AND (
461  (frm_posts.pos_date >= %s AND frm_posts.pos_date = frm_posts.pos_activation_date) OR
462  (frm_posts.pos_activation_date >= %s AND frm_posts.pos_date < frm_posts.pos_activation_date)
463  ) ';
464  $types = array('integer', 'timestamp', 'timestamp');
465  $values = array(1, $threshold_date, $threshold_date);
466 
467  $res = $this->ilDB->queryf(
468  $this->createForumPostSql($condition),
469  $types,
470  $values
471  );
472 
473  $this->sendNotification(
474  $res,
475  'new posting',
477  );
478  }
479 
483  private function sendNotificationForUpdatedPosts(string $threshold_date)
484  {
485  $condition = '
486  frm_posts.pos_cens = %s AND frm_posts.pos_status = %s AND
487  (frm_posts.pos_update > frm_posts.pos_date AND frm_posts.pos_update >= %s) ';
488  $types = array('integer', 'integer', 'timestamp');
489  $values = array(0, 1, $threshold_date);
490 
491  $res = $this->ilDB->queryf(
492  $this->createForumPostSql($condition),
493  $types,
494  $values
495  );
496 
497  $this->sendNotification(
498  $res,
499  'updated posting',
501  );
502  }
503 
507  private function sendNotificationForCensoredPosts(string $threshold_date)
508  {
509  $condition = '
510  frm_posts.pos_cens = %s AND frm_posts.pos_status = %s AND
511  (frm_posts.pos_cens_date >= %s AND frm_posts.pos_cens_date > frm_posts.pos_activation_date ) ';
512  $types = array('integer', 'integer', 'timestamp');
513  $values = array(1, 1, $threshold_date);
514 
515  $res = $this->ilDB->queryf(
516  $this->createForumPostSql($condition),
517  $types,
518  $values
519  );
520 
521  $this->sendNotification(
522  $res,
523  'censored posting',
525  );
526  }
527 
531  private function sendNotificationForUncensoredPosts(string $threshold_date)
532  {
533  $condition = '
534  frm_posts.pos_cens = %s AND frm_posts.pos_status = %s AND
535  (frm_posts.pos_cens_date >= %s AND frm_posts.pos_cens_date > frm_posts.pos_activation_date ) ';
536  $types = array('integer', 'integer', 'timestamp');
537  $values = array(0, 1, $threshold_date);
538 
539  $res = $this->ilDB->queryf(
540  $this->createForumPostSql($condition),
541  $types,
542  $values
543  );
544 
545  $this->sendNotification(
546  $res,
547  'uncensored posting',
549  );
550  }
551 
553  {
554  $res = $this->ilDB->queryF(
556  array('integer'),
557  array(1)
558  );
559 
560  $this->sendDeleteNotifcations(
561  $res,
562  'frm_threads_deleted',
563  'deleted threads',
565  );
566  }
567 
569  {
570  $res = $this->ilDB->queryF(
572  array('integer'),
573  array(0)
574  );
575 
576  $this->sendDeleteNotifcations(
577  $res,
578  'frm_posts_deleted',
579  'deleted postings',
581  );
582  }
583 
589  private function sendNotification(\ilPDOStatement $res, string $actionName, int $notificationType)
590  {
591  $numRows = $this->ilDB->numRows($res);
592  if ($numRows > 0) {
593  $this->logger->info(sprintf('Sending notifications for %s "%s" events ...', $numRows, $actionName));
594  $this->sendCronForumNotification($res, $notificationType);
595  $this->logger->info(sprintf('Sent notifications for %s ...', $actionName));
596  }
597 
598  $this->keepAlive();
599  }
600 
607  private function sendDeleteNotifcations(\ilPDOStatement $res, string $action, string $actionDescription, int $notificationType)
608  {
609  $numRows = $this->ilDB->numRows($res);
610  if ($numRows > 0) {
611  $this->logger->info(sprintf('Sending notifications for %s "%s" events ...', $numRows, $actionDescription));
612  $this->sendCronForumNotification($res, $notificationType);
613  if (count(self::$deleted_ids_cache) > 0) {
614  $this->ilDB->manipulate('DELETE FROM frm_posts_deleted WHERE ' . $this->ilDB->in('deleted_id', self::$deleted_ids_cache, false, 'integer'));
615  $this->logger->info('Deleted obsolete entries of table "' . $action . '" ...');
616  }
617  $this->logger->info(sprintf('Sent notifications for %s ...', $actionDescription));
618  }
619 
620  $this->keepAlive();
621  }
622 
627  private function createForumPostSql($condition) : string
628  {
629  return '
630  SELECT frm_threads.thr_subject thr_subject,
631  frm_data.top_name top_name,
632  frm_data.top_frm_fk obj_id,
633  frm_notification.user_id user_id,
634  frm_threads.thr_pk thread_id,
635  frm_posts.*
636  FROM frm_notification, frm_posts, frm_threads, frm_data, frm_posts_tree
637  WHERE frm_posts.pos_thr_fk = frm_threads.thr_pk AND ' . $condition . '
638  AND ((frm_threads.thr_top_fk = frm_data.top_pk AND frm_data.top_frm_fk = frm_notification.frm_id)
639  OR (frm_threads.thr_pk = frm_notification.thread_id
640  AND frm_data.top_pk = frm_threads.thr_top_fk) )
641  AND frm_posts.pos_display_user_id != frm_notification.user_id
642  AND frm_posts_tree.pos_fk = frm_posts.pos_pk AND frm_posts_tree.parent_pos != 0
643  ORDER BY frm_posts.pos_date ASC';
644  }
645 
649  private function createSelectOfDeletionNotificationsSql() : string
650  {
651  return '
652  SELECT frm_posts_deleted.thread_title thr_subject,
653  frm_posts_deleted.forum_title top_name,
654  frm_posts_deleted.obj_id obj_id,
655  frm_notification.user_id user_id,
656  frm_posts_deleted.pos_display_user_id,
657  frm_posts_deleted.pos_usr_alias,
658  frm_posts_deleted.deleted_id,
659  frm_posts_deleted.post_date pos_date,
660  frm_posts_deleted.post_title pos_subject,
661  frm_posts_deleted.post_message pos_message,
662  frm_posts_deleted.deleted_by
663 
664  FROM frm_notification, frm_posts_deleted
665 
666  WHERE ( frm_posts_deleted.obj_id = frm_notification.frm_id
667  OR frm_posts_deleted.thread_id = frm_notification.thread_id)
668  AND frm_posts_deleted.pos_display_user_id != frm_notification.user_id
669  AND frm_posts_deleted.is_thread_deleted = %s
670  ORDER BY frm_posts_deleted.post_date ASC';
671  }
672 }
getFirstAccessibleRefIdBUserAndObjId($a_user_id, $a_obj_id)
settings()
Definition: settings.php:2
$result
This class represents a property form user interface.
sendCronForumNotification($res, $notification_type)
Class ilPDOStatement is a Wrapper Class for PDOStatement.
Cron job application base class.
Class ilForumCronNotificationDataProvider.
sendNotification(\ilPDOStatement $res, string $actionName, int $notificationType)
sendNotificationForUncensoredPosts(string $threshold_date)
addItem($a_item)
Add Item (Property, SectionHeader).
static _getAllReferences($a_id)
get all reference ids of object
$container
Definition: wac.php:13
addCustomSettingsToForm(ilPropertyFormGUI $a_form)
foreach($_POST as $key=> $value) $res
sendNotificationForCensoredPosts(string $threshold_date)
$lng
This class represents a number property in a property form.
global $DIC
Definition: goto.php:24
Class ilForumNotificationCache.
sendNotificationForNewPosts(string $threshold_date)
saveCustomSettings(ilPropertyFormGUI $a_form)
getInput($a_post_var, $ensureValidation=true)
Returns the value of a HTTP-POST variable, identified by the passed id.
addToExternalSettingsForm($a_form_id, array &$a_fields, $a_is_active)
setSize($a_size)
Set Size.
static ping($a_job_id)
Keep cron job alive.
global $ilSetting
Definition: privfeed.php:17
static getInstanceByRefId($a_ref_id, $stop_on_error=true)
get an instance of an Ilias object by reference id
Class ilObjGroup.
sendNotificationForUpdatedPosts(string $threshold_date)
sendDeleteNotifcations(\ilPDOStatement $res, string $action, string $actionDescription, int $notificationType)
Cron job result data container.
__construct(\ilDBInterface $database=null, \ilForumNotificationCache $notificationCache=null)
$i
Definition: metadata.php:24