Elgg  Version 2.3
NotificationsService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Notifications;
4 
16 
27 
28  const QUEUE_NAME = 'notifications';
29 
31  protected $subscriptions;
32 
34  protected $queue;
35 
37  protected $hooks;
38 
40  protected $session;
41 
43  protected $translator;
44 
46  protected $entities;
47 
49  protected $logger;
50 
52  protected $events = array();
53 
55  protected $methods = array();
56 
58  protected $deprHandlers = array();
59 
61  protected $deprSubjects = array();
62 
74  public function __construct(
80  Logger $logger) {
81 
82  $this->subscriptions = $subscriptions;
83  $this->queue = $queue;
84  $this->hooks = $hooks;
85  $this->session = $session;
86  $this->translator = $translator;
87  $this->entities = $entities;
88  $this->logger = $logger;
89  }
90 
95  public function registerEvent($type, $subtype, array $actions = array()) {
96 
97  if (!isset($this->events[$type])) {
98  $this->events[$type] = array();
99  }
100  if (!isset($this->events[$type][$subtype])) {
101  $this->events[$type][$subtype] = array();
102  }
103 
104  $action_list =& $this->events[$type][$subtype];
105  if ($actions) {
106  $action_list = array_unique(array_merge($action_list, $actions));
107  } elseif (!in_array('create', $action_list)) {
108  $action_list[] = 'create';
109  }
110  }
111 
116  public function unregisterEvent($type, $subtype) {
117 
118  if (!isset($this->events[$type]) || !isset($this->events[$type][$subtype])) {
119  return false;
120  }
121 
122  unset($this->events[$type][$subtype]);
123 
124  return true;
125  }
126 
130  public function getEvents() {
131  return $this->events;
132  }
133 
138  public function registerMethod($name) {
139  $this->methods[$name] = $name;
140  }
141 
146  public function unregisterMethod($name) {
147  if (isset($this->methods[$name])) {
148  unset($this->methods[$name]);
149  return true;
150  }
151  return false;
152  }
153 
157  public function getMethods() {
158  return $this->methods;
159  }
160 
170  public function enqueueEvent($action, $type, $object) {
171 
172  if ($object instanceof ElggData) {
173  $object_type = $object->getType();
174  $object_subtype = $object->getSubtype();
175 
176  $registered = false;
177  if (!empty($this->events[$object_type][$object_subtype]) && in_array($action, $this->events[$object_type][$object_subtype])) {
178  $registered = true;
179  }
180 
181  if ($registered) {
182  $params = array(
183  'action' => $action,
184  'object' => $object,
185  );
186  $registered = $this->hooks->trigger('enqueue', 'notification', $params, $registered);
187  }
188 
189  if ($registered) {
190  $this->queue->enqueue(new SubscriptionNotificationEvent($object, $action));
191  }
192  }
193  }
194 
203  public function processQueue($stopTime, $matrix = false) {
204 
205  $this->subscriptions->methods = $this->methods;
206 
207  $delivery_matrix = [];
208 
209  $count = 0;
210 
211  // @todo grab mutex
212 
213  $ia = $this->session->setIgnoreAccess(true);
214 
215  while (time() < $stopTime) {
216  // dequeue notification event
217  $event = $this->queue->dequeue();
218  /* @var $event NotificationEvent */
219 
220  if (!$event) {
221  // queue is empty
222  break;
223  }
224 
225  if (!$event instanceof NotificationEvent || !$event->getObject() || !$event->getActor()) {
226  // event object or actor have been deleted since the event was enqueued
227  continue;
228  }
229 
230  // test for usage of the deprecated override hook
231  if ($this->existsDeprecatedNotificationOverride($event)) {
232  continue;
233  }
234 
235  $subscriptions = $this->subscriptions->getSubscriptions($event);
236 
237  // return false to stop the default notification sender
238  $params = [
239  'event' => $event,
240  'subscriptions' => $subscriptions
241  ];
242 
243  $deliveries = [];
244  if ($this->hooks->trigger('send:before', 'notifications', $params, true)) {
245  $deliveries = $this->sendNotifications($event, $subscriptions);
246  }
247  $params['deliveries'] = $deliveries;
248  $this->hooks->trigger('send:after', 'notifications', $params);
249  $count++;
250 
251  $delivery_matrix[$event->getDescription()] = $deliveries;
252  }
253 
254  // release mutex
255 
256  $this->session->setIgnoreAccess($ia);
257 
258  return $matrix ? $delivery_matrix : $count;
259  }
260 
281  protected function sendNotifications($event, $subscriptions, array $params = []) {
282 
283  if (!$this->methods) {
284  return 0;
285  }
286 
287  $result = [];
288  foreach ($subscriptions as $guid => $methods) {
289  foreach ($methods as $method) {
290  $result[$guid][$method] = false;
291  if (in_array($method, $this->methods)) {
292  $result[$guid][$method] = $this->sendNotification($event, $guid, $method, $params);
293  }
294  }
295  }
296 
297  $this->logger->notice("Results for the notification event {$event->getDescription()}: " . print_r($result, true));
298  return $result;
299  }
300 
338  public function sendInstantNotifications(\ElggEntity $sender, array $recipients = [], array $params = []) {
339 
340  if (!$sender instanceof \ElggEntity) {
341  throw new InvalidArgumentException("Notification sender must be a valid entity");
342  }
343 
344  $deliveries = [];
345 
346  if (!$this->methods) {
347  return $deliveries;
348  }
349 
350  $recipients = array_filter($recipients, function($e) {
351  return ($e instanceof \ElggUser);
352  });
353 
354  $object = elgg_extract('object', $params);
355  $action = elgg_extract('action', $params);
356 
357  $methods_override = elgg_extract('methods_override', $params);
358  unset($params['methods_override']);
359  if ($methods_override && !is_array($methods_override)) {
360  $methods_override = [$methods_override];
361  }
362 
363  $event = new InstantNotificationEvent($object, $action, $sender);
364 
365  $params['event'] = $event;
367 
368  $subscriptions = [];
369 
370  foreach ($recipients as $recipient) {
371 
372  // Are we overriding delivery?
373  $methods = $methods_override;
374  if (empty($methods)) {
375  $methods = [];
376  $user_settings = $recipient->getNotificationSettings();
377  foreach ($user_settings as $method => $enabled) {
378  if ($enabled) {
379  $methods[] = $method;
380  }
381  }
382  }
383 
384  $subscriptions[$recipient->guid] = $methods;
385  }
386 
387  $hook_params = [
388  'event' => $params['event'],
389  'origin' => $params['origin'],
390  'methods_override' => $methods_override,
391  ];
392  $subscriptions = $this->hooks->trigger('get', 'subscriptions', $hook_params, $subscriptions);
393 
394  $params['subscriptions'] = $subscriptions;
395 
396  // return false to stop the default notification sender
397  if ($this->hooks->trigger('send:before', 'notifications', $params, true)) {
398  $deliveries = $this->sendNotifications($event, $subscriptions, $params);
399  }
400  $params['deliveries'] = $deliveries;
401  $this->hooks->trigger('send:after', 'notifications', $params);
402 
403  return $deliveries;
404  }
405 
416  protected function sendNotification(NotificationEvent $event, $guid, $method, array $params = []) {
417 
418  $actor = $event->getActor();
419  $object = $event->getObject();
420 
421  if ($event instanceof InstantNotificationEvent) {
422  $recipient = $this->entities->get($guid);
423  /* @var \ElggEntity $recipient */
424  $subject = elgg_extract('subject', $params, '');
425  $body = elgg_extract('body', $params, '');
426  $summary = elgg_extract('summary', $params, '');
427  } else {
428  $recipient = $this->entities->get($guid, 'user');
429  /* @var \ElggUser $recipient */
430  if (!$recipient || $recipient->isBanned()) {
431  return false;
432  }
433 
434  if ($recipient->getGUID() == $event->getActorGUID()) {
435  // Content creators should not be receiving subscription
436  // notifications about their own content
437  return false;
438  }
439 
440  if (!$actor || !$object) {
441  return false;
442  }
443 
444  if ($object instanceof ElggEntity && !has_access_to_entity($object, $recipient)) {
445  // Recipient does not have access to the notification object
446  // The access level may have changed since the event was enqueued
447  return false;
448  }
449 
450  $subject = $this->getNotificationSubject($event, $recipient);
451  $body = $this->getNotificationBody($event, $recipient);
452  $summary = '';
453 
455  }
456 
457  $language = $recipient->language;
458  $params['event'] = $event;
459  $params['method'] = $method;
460  $params['sender'] = $actor;
461  $params['recipient'] = $recipient;
462  $params['language'] = $language;
463  $params['object'] = $object;
464  $params['action'] = $event->getAction();
465 
466  $notification = new Notification($actor, $recipient, $language, $subject, $body, $summary, $params);
467 
468  $notification = $this->hooks->trigger('prepare', 'notification', $params, $notification);
469  if (!$notification instanceof Notification) {
470  throw new RuntimeException("'prepare','notification' hook must return an instance of " . Notification::class);
471  }
472 
473  $type = 'notification:' . $event->getDescription();
474  if ($this->hooks->hasHandler('prepare', $type)) {
475  $notification = $this->hooks->trigger('prepare', $type, $params, $notification);
476  if (!$notification instanceof Notification) {
477  throw new RuntimeException("'prepare','$type' hook must return an instance of " . Notification::class);
478  }
479  } else {
480  // pre Elgg 1.9 notification message generation
481  $notification = $this->getDeprecatedNotificationBody($notification, $event, $method);
482  }
483 
484  $notification = $this->hooks->trigger('format', "notification:$method", [], $notification);
485  if (!$notification instanceof Notification) {
486  throw new RuntimeException("'format','notification:$method' hook must return an instance of " . Notification::class);
487  }
488 
489  if ($this->hooks->hasHandler('send', "notification:$method")) {
490  // return true to indicate the notification has been sent
491  $params = array(
492  'notification' => $notification,
493  'event' => $event,
494  );
495 
496  $result = $this->hooks->trigger('send', "notification:$method", $params, false);
497  if ($this->logger->getLevel() == Logger::INFO) {
498  $logger_data = print_r((array) $notification->toObject(), true);
499  if ($result) {
500  $this->logger->info("Notification sent: " . $logger_data);
501  } else {
502  $this->logger->info("Notification was not sent: " . $logger_data);
503  }
504  }
505  return $result;
506  } else {
507  // pre Elgg 1.9 notification handler
508  $userGuid = $notification->getRecipientGUID();
509  $senderGuid = $notification->getSenderGUID();
510  $subject = $notification->subject;
511  $body = $notification->body;
512  $params = $notification->params;
513  return (bool) _elgg_notify_user($userGuid, $senderGuid, $subject, $body, $params, array($method));
514  }
515  }
516 
531  private function getNotificationSubject(NotificationEvent $event, ElggUser $recipient) {
532  $actor = $event->getActor();
533  $object = $event->getObject();
534  /* @var \ElggObject $object */
535  $language = $recipient->language;
536 
537  // Check custom notification subject for the action/type/subtype combination
538  $subject_key = "notification:{$event->getDescription()}:subject";
539  if ($this->translator->languageKeyExists($subject_key, $language)) {
540  if ($object instanceof \ElggEntity) {
541  $display_name = $object->getDisplayName();
542  } else {
543  $display_name = '';
544  }
545  return $this->translator->translate($subject_key, array(
546  $actor->name,
548  ), $language);
549  }
550 
551  // Fall back to default subject
552  return $this->translator->translate('notification:subject', array($actor->name), $language);
553  }
554 
591  private function getNotificationBody(NotificationEvent $event, ElggUser $recipient) {
592  $actor = $event->getActor();
593  $object = $event->getObject();
594  /* @var \ElggObject $object */
595  $language = $recipient->language;
596 
597  // Check custom notification body for the action/type/subtype combination
598  $body_key = "notification:{$event->getDescription()}:body";
599  if ($this->translator->languageKeyExists($body_key, $language)) {
600  if ($object instanceof \ElggEntity) {
601  $display_name = $object->getDisplayName();
602  $container_name = '';
603  $container = $object->getContainerEntity();
604  if ($container) {
605  $container_name = $container->getDisplayName();
606  }
607  } else {
608  $display_name = '';
609  $container_name = '';
610  }
611 
612  return $this->translator->translate($body_key, array(
613  $recipient->name,
614  $actor->name,
616  $container_name,
617  $object->description,
618  $object->getURL(),
619  ), $language);
620  }
621 
622  // Fall back to default body
623  return $this->translator->translate('notification:body', array($object->getURL()), $language);
624  }
625 
634  $this->deprHandlers[$method] = $handler;
635  }
636 
643  public function getDeprecatedHandler($method) {
644  if (isset($this->deprHandlers[$method])) {
645  return $this->deprHandlers[$method];
646  } else {
647  return null;
648  }
649  }
650 
657  public function getMethodsAsDeprecatedGlobal() {
658  $data = array();
659  foreach ($this->methods as $method) {
660  $data[$method] = 'empty';
661  }
662  return $data;
663  }
664 
673  protected function getDeprecatedNotificationBody(Notification $notification, NotificationEvent $event, $method) {
674  $entity = $event->getObject();
675  if (!$entity) {
676  return $notification;
677  }
678  $params = array(
679  'entity' => $entity,
680  'to_entity' => $notification->getRecipient(),
681  'method' => $method,
682  );
683  $subject = $this->getDeprecatedNotificationSubject($entity->getType(), $entity->getSubtype());
684  $string = $subject . ": " . $entity->getURL();
685  $body = $this->hooks->trigger('notify:entity:message', $entity->getType(), $params, $string);
686 
687  if ($subject) {
688  $notification->subject = $subject;
689  $notification->body = $body;
690  }
691 
692  return $notification;
693  }
694 
704  if ($type == '') {
705  $type = '__BLANK__';
706  }
707  if ($subtype == '') {
708  $subtype = '__BLANK__';
709  }
710 
711  if (!isset($this->deprSubjects[$type])) {
712  $this->deprSubjects[$type] = array();
713  }
714 
715  $this->deprSubjects[$type][$subtype] = $subject;
716  }
717 
726  if ($type == '') {
727  $type = '__BLANK__';
728  }
729  if ($subtype == '') {
730  $subtype = '__BLANK__';
731  }
732 
733  if (!isset($this->deprSubjects[$type])) {
734  return '';
735  }
736 
737  if (!isset($this->deprSubjects[$type][$subtype])) {
738  return '';
739  }
740 
741  return $this->deprSubjects[$type][$subtype];
742  }
743 
751  $entity = $event->getObject();
752  if (!elgg_instanceof($entity)) {
753  return false;
754  }
755  $params = array(
756  'event' => $event->getAction(),
757  'object_type' => $entity->getType(),
758  'object' => $entity,
759  );
760  $hookresult = $this->hooks->trigger('object:notifications', $entity->getType(), $params, false);
761  if ($hookresult === true) {
762  elgg_deprecated_notice("Using the plugin hook 'object:notifications' has been deprecated "
763  . "by the hook 'send:before', 'notifications'", 1.9);
764  return true;
765  } else {
766  return false;
767  }
768  }
769 
770 }
sendNotifications($event, $subscriptions, array $params=[])
Sends the notifications based on subscriptions.
getObject()
Get the object of the event.
getRecipient()
Get the recipient entity.
$object
These two snippets demonstrates triggering an event and how to register for that event.
Definition: trigger.php:7
$display_name
Definition: delete.php:22
$action
Definition: full.php:133
$subject
Definition: exceptions.php:25
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
$method
Definition: form.php:25
getDescription()
Get a description of the event.
$e
Definition: metadata.php:12
getActorGUID()
Get the GUID of the actor.
$data
Definition: opendd.php:13
$subtype
Definition: delete.php:28
getAction()
Get the name of the action.
$guid
Removes an admin notice.
existsDeprecatedNotificationOverride(NotificationEvent $event)
Is someone using the deprecated override.
getMethodsAsDeprecatedGlobal()
Provides a way to incrementally wean Elgg&#39;s notifications code from the global $NOTIFICATION_HANDLERS...
getDeprecatedHandler($method)
Get a deprecated notification handler callback.
setDeprecatedNotificationSubject($type, $subtype, $subject)
Set message subject for deprecated notification code.
$string
$actions
Definition: user_hover.php:12
getActor()
Get the actor of the event.
$params
Definition: login.php:72
WARNING: API IN FLUX.
Definition: Translator.php:11
__construct(SubscriptionsService $subscriptions, Queue $queue, PluginHooksService $hooks, ElggSession $session, Translator $translator, EntityTable $entities, Logger $logger)
Constructor.
elgg_instanceof($entity, $type=null, $subtype=null, $class=null)
Checks if $entity is an and optionally for type and subtype.
Definition: entities.php:736
$container
Definition: delete.php:29
$language
Definition: useradd.php:20
sendNotification(NotificationEvent $event, $guid, $method, array $params=[])
Send a notification to a subscriber.
elgg ElggUser
Definition: ElggUser.js:12
elgg_deprecated_notice($msg, $dep_version, $backtrace_level=1)
Log a notice about deprecated use of a function, view, etc.
Definition: elgglib.php:1098
getDeprecatedNotificationBody(Notification $notification, NotificationEvent $event, $method)
Get the notification body using a pre-Elgg 1.9 plugin hook.
enqueueEvent($action, $type, $object)
Add a notification event to the queue.
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:1375
elgg subtext time
getDeprecatedNotificationSubject($type, $subtype)
Get the deprecated subject.
$entity
Definition: delete.php:7
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:237
$handler
Definition: add.php:10
processQueue($stopTime, $matrix=false)
Pull notification events from queue until stop time is reached.
$summary
Definition: header.php:12
const INFO
Definition: Logger.php:21
registerEvent($type, $subtype, array $actions=array())
registerDeprecatedHandler($method, $handler)
Register a deprecated notification handler.
elgg ElggEntity
Definition: ElggEntity.js:16
$enabled
CI CLI installer script.
Definition: ci_installer.php:8
http free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:5
_elgg_notify_user($to, $from, $subject, $message, array $params=null, $methods_override="")
Notify a user via their preferences.
if(!$display_name) $type
Definition: delete.php:27