Elgg  Version 3.0
NotificationsService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Notifications;
4 
7 use Elgg\Loggable;
8 use Elgg\Logger;
11 use ElggData;
12 use ElggEntity;
13 use ElggSession;
14 use ElggUser;
19 
30 
31  use Loggable;
32 
33  const QUEUE_NAME = 'notifications';
34 
36  protected $subscriptions;
37 
39  protected $queue;
40 
42  protected $hooks;
43 
45  protected $session;
46 
48  protected $translator;
49 
51  protected $entities;
52 
54  protected $events = [];
55 
57  protected $methods = [];
58 
60  protected $deprHandlers = [];
61 
63  protected $deprSubjects = [];
64 
76  public function __construct(
82  LoggerInterface $logger
83  ) {
84 
85  $this->subscriptions = $subscriptions;
86  $this->queue = $queue;
87  $this->hooks = $hooks;
88  $this->session = $session;
89  $this->translator = $translator;
90  $this->entities = $entities;
91  $this->logger = $logger;
92  }
93 
107  public function registerEvent($type, $subtype, array $actions = []) {
108 
109  if (!isset($this->events[$type])) {
110  $this->events[$type] = [];
111  }
112  if (!isset($this->events[$type][$subtype])) {
113  $this->events[$type][$subtype] = [];
114  }
115 
116  $action_list =& $this->events[$type][$subtype];
117  if (!empty($actions)) {
118  $action_list = array_unique(array_merge($action_list, $actions));
119  } elseif (!in_array('create', $action_list)) {
120  $action_list[] = 'create';
121  }
122  }
123 
134  public function unregisterEvent($type, $subtype) {
135 
136  if (!isset($this->events[$type]) || !isset($this->events[$type][$subtype])) {
137  return false;
138  }
139 
140  unset($this->events[$type][$subtype]);
141 
142  return true;
143  }
144 
150  public function getEvents() {
151  return $this->events;
152  }
153 
162  public function registerMethod($name) {
163  $this->methods[$name] = $name;
164  }
165 
174  public function unregisterMethod($name) {
175  if (isset($this->methods[$name])) {
176  unset($this->methods[$name]);
177  return true;
178  }
179  return false;
180  }
181 
189  public function getMethods() {
190  return $this->methods;
191  }
192 
201  public function enqueueEvent($action, $type, $object) {
202 
203  if ($object instanceof ElggData) {
204  $object_type = $object->getType();
205  $object_subtype = $object->getSubtype();
206 
207  $registered = false;
208  if (!empty($this->events[$object_type][$object_subtype]) && in_array($action, $this->events[$object_type][$object_subtype])) {
209  $registered = true;
210  }
211 
212  if ($registered) {
213  $params = [
214  'action' => $action,
215  'object' => $object,
216  ];
217  $registered = $this->hooks->trigger('enqueue', 'notification', $params, $registered);
218  }
219 
220  if ($registered) {
221  $this->queue->enqueue(new SubscriptionNotificationEvent($object, $action));
222  }
223  }
224  }
225 
233  public function processQueue($stopTime, $matrix = false) {
234 
235  $this->subscriptions->methods = $this->methods;
236 
237  $delivery_matrix = [];
238 
239  $count = 0;
240 
241  // @todo grab mutex
242 
243  $ia = $this->session->setIgnoreAccess(true);
244 
245  while (time() < $stopTime) {
246  // dequeue notification event
247  $event = $this->queue->dequeue();
248  /* @var $event NotificationEvent */
249 
250  if (!$event) {
251  // queue is empty
252  break;
253  }
254 
255  if (!$event instanceof NotificationEvent || !$event->getObject() || !$event->getActor()) {
256  // event object or actor have been deleted since the event was enqueued
257  continue;
258  }
259 
260  $subscriptions = $this->subscriptions->getSubscriptions($event);
261 
262  // return false to stop the default notification sender
263  $params = [
264  'event' => $event,
265  'subscriptions' => $subscriptions
266  ];
267 
268  $deliveries = [];
269  if ($this->hooks->trigger('send:before', 'notifications', $params, true)) {
270  $deliveries = $this->sendNotifications($event, $subscriptions);
271  }
272  $params['deliveries'] = $deliveries;
273  $this->hooks->trigger('send:after', 'notifications', $params);
274  $count++;
275 
276  $delivery_matrix[$event->getDescription()] = $deliveries;
277  }
278 
279  // release mutex
280 
281  $this->session->setIgnoreAccess($ia);
282 
283  return $matrix ? $delivery_matrix : $count;
284  }
285 
305  protected function sendNotifications($event, $subscriptions, array $params = []) {
306 
307  if (empty($this->methods)) {
308  return [];
309  }
310 
311  $result = [];
312  foreach ($subscriptions as $guid => $methods) {
313  foreach ($methods as $method) {
314  $result[$guid][$method] = false;
315  if (in_array($method, $this->methods)) {
316  $result[$guid][$method] = $this->sendNotification($event, $guid, $method, $params);
317  }
318  }
319  }
320 
321  $this->logger->info("Results for the notification event {$event->getDescription()}: " . print_r($result, true));
322  return $result;
323  }
324 
361  public function sendInstantNotifications(\ElggEntity $sender, array $recipients = [], array $params = []) {
362 
363  $deliveries = [];
364 
365  if (empty($this->methods)) {
366  return $deliveries;
367  }
368 
369  $recipients = array_filter($recipients, function($e) {
370  return ($e instanceof \ElggUser);
371  });
372 
373  $object = elgg_extract('object', $params);
374  $action = elgg_extract('action', $params);
375 
376  $methods_override = elgg_extract('methods_override', $params);
377  unset($params['methods_override']);
378  if ($methods_override && !is_array($methods_override)) {
379  $methods_override = [$methods_override];
380  }
381 
382  $event = new InstantNotificationEvent($object, $action, $sender);
383 
384  $params['event'] = $event;
386 
387  $subscriptions = [];
388 
389  foreach ($recipients as $recipient) {
390  // Are we overriding delivery?
391  $methods = $methods_override;
392  if (empty($methods)) {
393  $methods = [];
394  $user_settings = $recipient->getNotificationSettings();
395  foreach ($user_settings as $method => $enabled) {
396  if ($enabled) {
397  $methods[] = $method;
398  }
399  }
400  }
401 
402  $subscriptions[$recipient->guid] = $methods;
403  }
404 
405  $hook_params = [
406  'event' => $params['event'],
407  'origin' => $params['origin'],
408  'methods_override' => $methods_override,
409  ];
410  $subscriptions = $this->hooks->trigger('get', 'subscriptions', $hook_params, $subscriptions);
411 
412  $params['subscriptions'] = $subscriptions;
413 
414  // return false to stop the default notification sender
415  if ($this->hooks->trigger('send:before', 'notifications', $params, true)) {
416  $deliveries = $this->sendNotifications($event, $subscriptions, $params);
417  }
418  $params['deliveries'] = $deliveries;
419  $this->hooks->trigger('send:after', 'notifications', $params);
420 
421  return $deliveries;
422  }
423 
433  protected function sendNotification(NotificationEvent $event, $guid, $method, array $params = []) {
434 
435  $actor = $event->getActor();
436  $object = $event->getObject();
437 
438  if ($event instanceof InstantNotificationEvent) {
439  $recipient = $this->entities->get($guid);
440  /* @var \ElggEntity $recipient */
441  $subject = elgg_extract('subject', $params, '');
442  $body = elgg_extract('body', $params, '');
443  $summary = elgg_extract('summary', $params, '');
444  } else {
445  $recipient = $this->entities->get($guid, 'user');
446  /* @var \ElggUser $recipient */
447  if (!$recipient || $recipient->isBanned()) {
448  return false;
449  }
450 
451  if ($recipient->getGUID() == $event->getActorGUID()) {
452  // Content creators should not be receiving subscription
453  // notifications about their own content
454  return false;
455  }
456 
457  if (!$actor || !$object) {
458  return false;
459  }
460 
461  if ($object instanceof ElggEntity && !has_access_to_entity($object, $recipient)) {
462  // Recipient does not have access to the notification object
463  // The access level may have changed since the event was enqueued
464  return false;
465  }
466 
467  $subject = $this->getNotificationSubject($event, $recipient);
468  $body = $this->getNotificationBody($event, $recipient);
469  $summary = '';
470 
472  }
473 
474  $language = $recipient->language;
475  $params['event'] = $event;
476  $params['method'] = $method;
477  $params['sender'] = $actor;
478  $params['recipient'] = $recipient;
479  $params['language'] = $language;
480  $params['object'] = $object;
481  $params['action'] = $event->getAction();
482 
483  $notification = new Notification($actor, $recipient, $language, $subject, $body, $summary, $params);
484 
485  $notification = $this->hooks->trigger('prepare', 'notification', $params, $notification);
486  if (!$notification instanceof Notification) {
487  throw new RuntimeException("'prepare','notification' hook must return an instance of " . Notification::class);
488  }
489 
490  $type = 'notification:' . $event->getDescription();
491  if ($this->hooks->hasHandler('prepare', $type)) {
492  $notification = $this->hooks->trigger('prepare', $type, $params, $notification);
493  if (!$notification instanceof Notification) {
494  throw new RuntimeException("'prepare','$type' hook must return an instance of " . Notification::class);
495  }
496  } else {
497  // pre Elgg 1.9 notification message generation
498  $notification = $this->getDeprecatedNotificationBody($notification, $event, $method);
499  }
500 
501  $notification = $this->hooks->trigger('format', "notification:$method", [], $notification);
502  if (!$notification instanceof Notification) {
503  throw new RuntimeException("'format','notification:$method' hook must return an instance of " . Notification::class);
504  }
505 
506  if ($this->hooks->hasHandler('send', "notification:$method")) {
507  // return true to indicate the notification has been sent
508  $params = [
509  'notification' => $notification,
510  'event' => $event,
511  ];
512 
513  $result = $this->hooks->trigger('send', "notification:$method", $params, false);
514  if ($this->logger->isLoggable(LogLevel::INFO)) {
515  $logger_data = print_r((array) $notification->toObject(), true);
516  if ($result) {
517  $this->logger->info("Notification sent: " . $logger_data);
518  } else {
519  $this->logger->info("Notification was not sent: " . $logger_data);
520  }
521  }
522  return $result;
523  } else {
524  // pre Elgg 1.9 notification handler
525  $userGuid = $notification->getRecipientGUID();
526  $senderGuid = $notification->getSenderGUID();
527  $subject = $notification->subject;
528  $body = $notification->body;
529  $params = $notification->params;
530  return (bool) _elgg_notify_user($userGuid, $senderGuid, $subject, $body, $params, [$method]);
531  }
532  }
533 
548  private function getNotificationSubject(NotificationEvent $event, ElggUser $recipient) {
549  $actor = $event->getActor();
550  $object = $event->getObject();
551 
552  $language = $recipient->language;
553 
554  // Check custom notification subject for the action/type/subtype combination
555  $subject_key = "notification:{$event->getDescription()}:subject";
556  if ($this->translator->languageKeyExists($subject_key, $language)) {
557  $display_name = '';
558  if ($object instanceof \ElggEntity) {
559  $display_name = $object->getDisplayName();
560  }
561 
562  return $this->translator->translate($subject_key, [
563  $actor->getDisplayName(),
565  ], $language);
566  }
567 
568  // Fall back to default subject
569  return $this->translator->translate('notification:subject', [$actor->getDisplayName()], $language);
570  }
571 
608  private function getNotificationBody(NotificationEvent $event, ElggUser $recipient) {
609  $actor = $event->getActor();
610  $object = $event->getObject();
611  /* @var \ElggObject $object */
612  $language = $recipient->language;
613 
614  // Check custom notification body for the action/type/subtype combination
615  $body_key = "notification:{$event->getDescription()}:body";
616  if ($this->translator->languageKeyExists($body_key, $language)) {
617  if ($object instanceof \ElggEntity) {
618  $display_name = $object->getDisplayName();
619  $container_name = '';
620  $container = $object->getContainerEntity();
621  if ($container) {
622  $container_name = $container->getDisplayName();
623  }
624  } else {
625  $display_name = '';
626  $container_name = '';
627  }
628 
629  return $this->translator->translate($body_key, [
630  $recipient->getDisplayName(),
631  $actor->getDisplayName(),
633  $container_name,
634  $object->description,
635  $object->getURL(),
636  ], $language);
637  }
638 
639  // Fall back to default body
640  return $this->translator->translate('notification:body', [$object->getURL()], $language);
641  }
642 
650  public function registerDeprecatedHandler($method, $handler) {
651  $this->deprHandlers[$method] = $handler;
652  }
653 
660  public function getDeprecatedHandler($method) {
661  if (isset($this->deprHandlers[$method])) {
662  return $this->deprHandlers[$method];
663  } else {
664  return null;
665  }
666  }
667 
674  public function getMethodsAsDeprecatedGlobal() {
675  $data = [];
676  foreach ($this->methods as $method) {
677  $data[$method] = 'empty';
678  }
679  return $data;
680  }
681 
690  protected function getDeprecatedNotificationBody(Notification $notification, NotificationEvent $event, $method) {
691  $entity = $event->getObject();
692  if (!$entity) {
693  return $notification;
694  }
695  $params = [
696  'entity' => $entity,
697  'to_entity' => $notification->getRecipient(),
698  'method' => $method,
699  ];
700  $subject = $this->getDeprecatedNotificationSubject($entity->getType(), $entity->getSubtype());
701  $string = $subject . ": " . $entity->getURL();
702  $body = $this->hooks->trigger('notify:entity:message', $entity->getType(), $params, $string);
703 
704  if ($subject) {
705  $notification->subject = $subject;
706  $notification->body = $body;
707  }
708 
709  return $notification;
710  }
711 
721  if ($type == '') {
722  $type = '__BLANK__';
723  }
724  if ($subtype == '') {
725  $subtype = '__BLANK__';
726  }
727 
728  if (!isset($this->deprSubjects[$type])) {
729  $this->deprSubjects[$type] = [];
730  }
731 
732  $this->deprSubjects[$type][$subtype] = $subject;
733  }
734 
743  if ($type == '') {
744  $type = '__BLANK__';
745  }
746  if ($subtype == '') {
747  $subtype = '__BLANK__';
748  }
749 
750  if (!isset($this->deprSubjects[$type])) {
751  return '';
752  }
753 
754  if (!isset($this->deprSubjects[$type][$subtype])) {
755  return '';
756  }
757 
758  return $this->deprSubjects[$type][$subtype];
759  }
760 }
sendNotifications($event, $subscriptions, array $params=[])
Sends the notifications based on subscriptions.
registerEvent($type, $subtype, array $actions=[])
Register a notification event.
getObject()
Get the object of the event.
getRecipient()
Get the recipient entity.
getType()
Get the type of the object.
$display_name
Definition: delete.php:19
$action
Definition: full.php:111
if(!$item instanceof ElggRiverItem) $object
Definition: responses.php:23
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
$params
Saves global plugin settings.
Definition: save.php:13
getDescription()
Get a description of the event.
getActorGUID()
Get the GUID of the actor.
trait Loggable
Enables adding a logger.
Definition: Loggable.php:12
$subtype
Definition: delete.php:22
if(elgg_trigger_plugin_hook('usersettings:save', 'user', $hooks_params, true)) foreach($request->validation() ->all() as $item) $data
Definition: save.php:57
getAction()
Get the name of the action.
$guid
Removes an admin notice.
getMethodsAsDeprecatedGlobal()
Provides a way to incrementally wean Elgg&#39;s notifications code from the global $NOTIFICATION_HANDLERS...
$type
Definition: delete.php:21
Queue interface.
Definition: Queue.php:16
getDeprecatedHandler($method)
Get a deprecated notification handler callback.
setDeprecatedNotificationSubject($type, $subtype, $subject)
Set message subject for deprecated notification code.
getSubtype()
Get the subtype of the object.
registerMethod($name)
Register a delivery method for notifications.
getActor()
Get the actor of the event.
$entity
Definition: reset.php:8
$container
Definition: delete.php:23
$language
Definition: useradd.php:17
sendNotification(NotificationEvent $event, $guid, $method, array $params=[])
Send a notification to a subscriber.
$enabled
Sample cli installer script.
unregisterMethod($name)
Unregister a delivery method for notifications.
elgg ElggUser
Definition: ElggUser.js:12
getEvents()
Return the notification events.
if(!$menu instanceof\Elgg\Menu\PreparedMenu) $actions
Definition: user_hover.php:16
getDeprecatedNotificationBody(Notification $notification, NotificationEvent $event, $method)
Get the notification body using a pre-Elgg 1.9 plugin hook.
getMethods()
Returns registered delivery methods for notifications.
$body
Definition: useradd.php:60
enqueueEvent($action, $type, $object)
Add a notification event to the queue.
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif(is_callable([$item, 'getType']))
Definition: item.php:39
sendInstantNotifications(\ElggEntity $sender, array $recipients=[], array $params=[])
Notify a user via their preferences.
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1131
__construct(SubscriptionsService $subscriptions, Queue $queue, PluginHooksService $hooks, ElggSession $session, Translator $translator, EntityTable $entities, LoggerInterface $logger)
Constructor.
getDeprecatedNotificationSubject($type, $subtype)
Get the deprecated subject.
if(elgg_in_context('widget')) $count
Definition: pagination.php:21
class
Definition: placeholder.php:21
has_access_to_entity($entity, $user=null)
Can a user access an entity.
Definition: access.php:188
unregisterEvent($type, $subtype)
Unregister a notification event.
$handler
Definition: add.php:7
processQueue($stopTime, $matrix=false)
Pull notification events from queue until stop time is reached.
$summary
Definition: header.php:24
registerDeprecatedHandler($method, $handler)
Register a deprecated notification handler.
elgg ElggEntity
Definition: ElggEntity.js:15
$subject
Definition: useradd.php:59
WARNING: API IN FLUX.
Definition: EntityTable.php:38
getDisplayName()
Get the entity&#39;s display name.
Definition: ElggEntity.php:307
_elgg_notify_user($to, $from, $subject, $message, array $params=null, $methods_override="")
Notify a user via their preferences.