Elgg  Version master
NotificationEventHandler.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Notifications;
4 
6 use Elgg\Traits\Loggable;
7 use Psr\Log\LogLevel;
8 
15 
16  use Loggable;
17 
21  protected array $params = [];
22 
26  protected array $methods_override = [];
27 
35  public function __construct(
36  protected NotificationEvent $event,
37  protected NotificationsService $service,
38  array $params = []
39  ) {
40  $methods_override = elgg_extract('methods_override', $params);
41  unset($params['methods_override']);
42  if (!empty($methods_override) && is_array($methods_override)) {
43  $this->setMethodsOverride($methods_override);
44  }
45 
46  $this->params = $params;
47  }
48 
57  final protected function setMethodsOverride(array $methods): void {
58  $this->methods_override = $methods;
59  }
60 
67  final protected function getMethodsOverride(): array {
68  return $this->methods_override;
69  }
70 
77  protected function getNotificationMethods(): array {
78  return [];
79  }
80 
86  final public function send(): array {
87  $deliveries = [];
88 
90  $params['handler'] = $this;
91  $params['event'] = $this->event;
92  $params['subscriptions'] = $this->prepareSubscriptions();
93 
94  // return false to stop the default notification sender
95  if (_elgg_services()->events->triggerResults('send:before', 'notifications', $params, true)) {
96  $deliveries = $this->sendNotifications($params['subscriptions'], $params);
97  }
98 
99  $params['deliveries'] = $deliveries;
100  _elgg_services()->events->triggerResults('send:after', 'notifications', $params);
101 
102  return $deliveries;
103  }
104 
110  final protected function prepareSubscriptions(): array {
111  $subscriptions = $this->getSubscriptions();
112 
113  $params = [
114  'event' => $this->event,
115  'methods' => $this->getMethods(),
116  'methods_override' => $this->getMethodsOverride(),
117  'handler' => $this,
118  ];
119  $subscriptions = _elgg_services()->events->triggerResults('get', 'subscriptions', $params, $subscriptions);
120 
121  return _elgg_services()->subscriptions->filterSubscriptions($subscriptions, $this->event, $this->filterMutedSubscriptions());
122  }
123 
130  protected function filterMutedSubscriptions(): bool {
131  return (bool) $this->getParam('apply_muting', true);
132  }
133 
139  public function getSubscriptions(): array {
140  return _elgg_services()->subscriptions->getNotificationEventSubscriptions($this->event, $this->getMethods(), $this->getNotificationSubscriptionExclusionGUIDs());
141  }
142 
148  final protected function getNotificationSubscriptionExclusionGUIDs(): array {
149  $object = $this->event->getObject();
150  if (!$object instanceof \ElggEntity) {
151  return [];
152  }
153 
154  $exclude = [];
155  if ($this->excludeOwnerSubscribers()) {
156  $exclude[] = $object->owner_guid;
157  }
158 
159  if ($this->excludeContainerSubscribers()) {
160  $exclude[] = $object->container_guid;
161  }
162 
163  if ($this->excludeEntitySubscribers()) {
164  $exclude[] = $object->guid;
165  }
166 
167  return $exclude;
168  }
169 
176  protected function excludeOwnerSubscribers(): bool {
177  return false;
178  }
179 
186  protected function excludeContainerSubscribers(): bool {
187  return false;
188  }
189 
196  protected function excludeEntitySubscribers(): bool {
197  return false;
198  }
199 
205  final public function getMethods(): array {
206  return $this->service->getMethods();
207  }
208 
228  final protected function sendNotifications(array $subscriptions, array $params = []): array {
229  if (empty($this->getMethods())) {
230  return [];
231  }
232 
233  $translator = _elgg_services()->translator;
234  $current_language = $translator->getCurrentLanguage();
235 
236  $result = [];
237  foreach ($subscriptions as $guid => $methods) {
238  $recipient = _elgg_services()->entityTable->get($guid);
239  if (!$recipient instanceof \ElggEntity) {
240  continue;
241  }
242 
243  if ($recipient instanceof \ElggUser) {
244  $translator->setCurrentLanguage($recipient->getLanguage());
245  }
246 
247  try {
248  foreach ($methods as $method) {
249  $result[$guid][$method] = false;
250 
251  if ($this->service->isRegisteredMethod($method)) {
252  $result[$guid][$method] = $this->sendNotification($recipient, $method, $params);
253  }
254  }
255  } catch (\Throwable $t) {
256  $translator->setCurrentLanguage($current_language);
257  throw $t;
258  }
259 
260  $translator->setCurrentLanguage($current_language);
261  }
262 
263  $this->getLogger()->info("Results for the notification event {$this->event->getDescription()}: " . print_r($result, true));
264  return $result;
265  }
266 
276  final protected function sendNotification(\ElggEntity $recipient, string $method, array $params = []): bool {
277  if (!_elgg_services()->events->hasHandler('send', "notification:{$method}")) {
278  // no way to deliver given the current method, so quitting early
279  return false;
280  }
281 
282  $actor = $this->event->getActor();
283  $object = $this->event->getObject();
284 
285  if (!$this->event instanceof InstantNotificationEvent) {
286  if (!$recipient instanceof \ElggUser || $recipient->isBanned()) {
287  return false;
288  }
289 
290  if (!$actor || !$object) {
291  return false;
292  }
293 
294  if ($object instanceof \ElggEntity && !$object->hasAccess($recipient->guid)) {
295  // Recipient does not have access to the notification object
296  // The access level may have changed since the event was enqueued
297  return false;
298  }
299  }
300 
301  $subject = ($recipient instanceof \ElggUser) ? $this->getNotificationSubject($recipient, $method) : '';
302  $body = ($recipient instanceof \ElggUser) ? $this->getNotificationBody($recipient, $method) : '';
303  $summary = ($recipient instanceof \ElggUser) ? $this->getNotificationSummary($recipient, $method) : '';
304  $url = ($recipient instanceof \ElggUser) ? $this->getNotificationURL($recipient, $method) : null;
305  $attachments = ($recipient instanceof \ElggUser) ? $this->getNotificationAttachments($recipient, $method) : null;
306 
307  $params['subject'] = $params['subject'] ?? $subject;
308  $params['body'] = $params['body'] ?? $body;
309  $params['summary'] = $params['summary'] ?? $summary;
310  $params['url'] = $params['url'] ?? $url;
311  $params['attachments'] = $params['attachments'] ?? $attachments;
312  $params['event'] = $this->event;
313  $params['method'] = $method;
314  $params['sender'] = $actor;
315  $params['recipient'] = $recipient;
316  $params['language'] = $recipient instanceof \ElggUser ? $recipient->getLanguage() : _elgg_services()->translator->getCurrentLanguage();
317  $params['object'] = $object;
318  $params['action'] = $this->event->getAction();
319  $params['add_salutation'] = elgg_extract('add_salutation', $params, true);
320  $params['add_mute_link'] = elgg_extract('add_mute_link', $params, $this->addMuteLink());
321 
322  $notification = $this->prepareNotification($params);
323  return $this->deliverNotification($notification, $method);
324  }
325 
334  final protected function deliverNotification(Notification $notification, string $method): bool {
335  // return true to indicate the notification has been sent
336  $params = [
337  'notification' => $notification,
338  'event' => $this->event,
339  ];
340 
341  $result = _elgg_services()->events->triggerResults('send', "notification:{$method}", $params, false);
342 
343  if ($this->getLogger()->isLoggable(LogLevel::INFO)) {
344  $logger_data = print_r((array) $notification->toObject(), true);
345  if ($result) {
346  $this->getLogger()->info('Notification sent: ' . $logger_data);
347  } else {
348  $this->getLogger()->info('Notification was not sent: ' . $logger_data);
349  }
350  }
351 
352  return $result;
353  }
354 
363  final protected function prepareNotification(array $params): Notification {
364  $notification = new Notification($params['sender'], $params['recipient'], $params['language'], $params['subject'], $params['body'], $params['summary'], $params);
365 
366  $notification = _elgg_services()->events->triggerResults('prepare', 'notification', $params, $notification);
367  if (!$notification instanceof Notification) {
368  throw new RuntimeException("'prepare','notification' event must return an instance of " . Notification::class);
369  }
370 
371  $type = 'notification:' . $this->event->getDescription();
372  $notification = _elgg_services()->events->triggerResults('prepare', $type, $params, $notification);
373  if (!$notification instanceof Notification) {
374  throw new RuntimeException("'prepare','{$type}' event must return an instance of " . Notification::class);
375  }
376 
377  if (elgg_extract('add_salutation', $notification->params) === true) {
378  $viewtype = elgg_view_exists('notifications/body') ? '' : 'default';
379  $notification->body = _elgg_view_under_viewtype('notifications/body', ['notification' => $notification], $viewtype);
380  }
381 
382  $notification = _elgg_services()->events->triggerResults('format', "notification:{$params['method']}", [], $notification);
383  if (!$notification instanceof Notification) {
384  throw new RuntimeException("'format','notification:{$params['method']}' event must return an instance of " . Notification::class);
385  }
386 
387  return $notification;
388  }
389 
401  protected function getNotificationSubject(\ElggUser $recipient, string $method): string {
402  $actor = $this->event->getActor() ?: null;
403  $object = $this->event->getObject();
404 
405  // Check custom notification subject for the action/type/subtype combination
406  $subject_key = "notification:{$this->event->getDescription()}:subject";
407  if (_elgg_services()->translator->languageKeyExists($subject_key)) {
408  return _elgg_services()->translator->translate($subject_key, [
409  $actor?->getDisplayName(),
410  $object instanceof \ElggEntity ? $object->getDisplayName() : '',
411  ]);
412  }
413 
414  // Fall back to default subject
415  return _elgg_services()->translator->translate('notification:subject', [$actor?->getDisplayName()]);
416  }
417 
440  protected function getNotificationBody(\ElggUser $recipient, string $method): string {
441  $actor = $this->event->getActor() ?: null;
442  $object = $this->event->getObject() ?: null;
443 
444  // Check custom notification body for the action/type/subtype combination
445  $body_key = "notification:{$this->event->getDescription()}:body";
446  if (_elgg_services()->translator->languageKeyExists($body_key)) {
447  $display_name = '';
448  $container_name = '';
449  if ($object instanceof \ElggEntity) {
450  $display_name = $object->getDisplayName();
451  $container_name = $object->getContainerEntity()?->getDisplayName();
452  }
453 
454  return _elgg_services()->translator->translate($body_key, [
455  $actor?->getDisplayName(),
457  $container_name,
459  $object?->getURL(),
460  ]);
461  }
462 
463  // Fall back to default body
464  return _elgg_services()->translator->translate('notification:body', [$object?->getURL()]);
465  }
466 
475  protected function getNotificationSummary(\ElggUser $recipient, string $method): string {
476  return '';
477  }
478 
487  protected function getNotificationURL(\ElggUser $recipient, string $method): string {
488  $object = $this->event->getObject() ?: null;
489 
490  return (string) $object?->getURL();
491  }
492 
501  protected function getNotificationAttachments(\ElggUser $recipient, string $method): array {
502  return [];
503  }
504 
511  protected function getEventActor(): ?\ElggUser {
512  $actor = $this->event->getActor();
513 
514  return $actor instanceof \ElggUser ? $actor : null;
515  }
516 
523  protected function getEventEntity(): ?\ElggEntity {
524  $object = $this->event->getObject();
525 
526  return $object instanceof \ElggEntity ? $object : null;
527  }
528 
534  public static function isConfigurableByUser(): bool {
535  return true;
536  }
537 
548  final public static function isConfigurableForEntity(\ElggEntity $entity): bool {
549  if ($entity instanceof \ElggUser) {
550  return static::isConfigurableForUser($entity);
551  } elseif ($entity instanceof \ElggGroup) {
552  return static::isConfigurableForGroup($entity);
553  }
554 
555  return false;
556  }
557 
566  protected static function isConfigurableForUser(\ElggUser $user): bool {
567  return true;
568  }
569 
580  protected static function isConfigurableForGroup(\ElggGroup $group): bool {
581  return true;
582  }
583 
589  protected function addMuteLink(): bool {
590  return true;
591  }
592 
601  final protected function getParam(string $param, mixed $default = null): mixed {
602  return elgg_extract($param, $this->params, $default);
603  }
604 }
getLogger()
Returns logger.
Definition: Loggable.php:37
$site description
Definition: settings.php:12
$entity
Definition: reset.php:8
$guid
Reset an ElggUpgrade.
Definition: reset.php:6
if(! $new_container instanceof \ElggEntity) if(! $new_container->canWriteToContainer(0, $entity->type, $entity->subtype)) $display_name
$type
Definition: delete.php:21
$recipient
Definition: mute.php:8
$params
Saves global plugin settings.
Definition: save.php:13
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/clear'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin', 'controller'=> \Elgg\Diagnostics\DownloadController::class,], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:76
if(! $owner instanceof ElggEntity) $summary
Definition: summary.php:33
$attachments
Outputs attachments.
Definition: attachments.php:9
$user
Definition: ban.php:7
foreach($categories as $key=> $category) $body
Definition: categories.php:38
Exception thrown if an error which can only be found on runtime occurs.
Notification Event Handler handles preparation of a notification.
__construct(protected NotificationEvent $event, protected NotificationsService $service, array $params=[])
Constructor.
sendNotification(\ElggEntity $recipient, string $method, array $params=[])
Send a notification to a subscriber.
deliverNotification(Notification $notification, string $method)
Deliver a notification.
addMuteLink()
Add a mute link in the email notification.
getMethods()
Returns methods to be used for this notification.
getNotificationSummary(\ElggUser $recipient, string $method)
Return the summary for a notification.
setMethodsOverride(array $methods)
Override the default user preferred delivery methods with the given methods.
filterMutedSubscriptions()
Should muted subscribers be filtered.
getEventActor()
Get the acting user from the notification event.
static isConfigurableByUser()
Is this event configurable by the user on the notification settings page.
excludeOwnerSubscribers()
Exclude the NotificationEvent object owner_guid when fetching the subscription records for this notif...
getEventEntity()
Get the entity from the notification event.
static isConfigurableForUser(\ElggUser $user)
Can this event be configured for a specific user.
static isConfigurableForGroup(\ElggGroup $group)
Can this event be configured for a specific group.
prepareNotification(array $params)
Prepares a notification for delivery.
getNotificationAttachments(\ElggUser $recipient, string $method)
Get the attachments for this notification.
getNotificationMethods()
Get the notification methods to use.
sendNotifications(array $subscriptions, array $params=[])
Sends the notifications based on subscriptions.
getNotificationSubject(\ElggUser $recipient, string $method)
Get subject for the notification.
static isConfigurableForEntity(\ElggEntity $entity)
Can this event be configured for a specific entity.
getNotificationBody(\ElggUser $recipient, string $method)
Get body for the notification.
excludeEntitySubscribers()
Exclude the NotificationEvent object guid when fetching the subscription records for this notificatio...
getNotificationSubscriptionExclusionGUIDs()
Get an array of GUIDs to not get the subscription records for.
getMethodsOverride()
Get the delivery methods override.
getParam(string $param, mixed $default=null)
Get a parameter from the notification parameters.
excludeContainerSubscribers()
Exclude the NotificationEvent object container_guid when fetching the subscription records for this n...
getSubscriptions()
Returns subscriptions for the event.
getNotificationURL(\ElggUser $recipient, string $method)
Returns the url related to this notification.
Notification container.
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
if($email instanceof \Elgg\Email) $object
Definition: body.php:24
$subject
HTML body of an email.
Definition: body.php:11
$notification
Definition: body.php:13
if($item instanceof \ElggEntity) elseif($item instanceof \ElggRiverItem) elseif($item instanceof \ElggRelationship) elseif(is_callable([ $item, 'getType']))
Definition: item.php:48
_elgg_services()
Get the global service provider.
Definition: elgglib.php:343
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:246
$default
Definition: checkbox.php:30
Notification event interface.
$viewtype
Definition: default.php:11
elgg_view_exists(string $view, string $viewtype='', bool $recurse=true)
Returns whether the specified view exists.
Definition: views.php:131
_elgg_view_under_viewtype(string $view, array $vars, string $viewtype)
Render a view while the global viewtype is temporarily changed.
Definition: views.php:1403
if(!elgg_get_config('trash_enabled')) $group
Definition: group.php:13
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$methods
Definition: subscribe.php:8
if(empty($methods)) $subscriptions
if(($owner instanceof \ElggGroup|| $owner instanceof \ElggUser) &&!in_array($owner->guid, $mute_guids)) $actor
Definition: mute.php:78