Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/lib/notification.php (6 issues)

1
<?php
2
/**
3
 * Adding a New Notification Event
4
 * ===============================
5
 * 1. Register the event with elgg_register_notification_event()
6
 *
7
 * 2. Register for the notification message plugin hook:
8
 *    'prepare', 'notification:[event name]'. The event name is of the form
9
 *    [action]:[type]:[subtype]. For example, the publish event for a blog
10
 *    would be named 'publish:object:blog'.
11
 *
12
 *    The parameter array for the plugin hook has the keys 'event', 'method',
13
 *    'recipient', and 'language'. The event is an \Elgg\Notifications\Event
14
 *    object and can provide access to the original object of the event through
15
 *    the method getObject() and the original actor through getActor().
16
 *
17
 *    The plugin hook callback modifies and returns a
18
 *    \Elgg\Notifications\Notification object that holds the message content.
19
 *
20
 *
21
 * Adding a Delivery Method
22
 * =========================
23
 * 1. Register the delivery method name with elgg_register_notification_method()
24
 *
25
 * 2. Register for the plugin hook for sending notifications:
26
 *    'send', 'notification:[method name]'. It receives the notification object
27
 *    of the namespace Elgg\Notifications;
28
 *
29
 *	  class Notification in the params array with the
30
 *    key 'notification'. The callback should return a boolean to indicate whether
31
 *    the message was sent.
32
 *
33
 *
34
 * Subscribing a User for Notifications
35
 * ====================================
36
 * Users subscribe to receive notifications based on container and delivery method.
37
 *
38
 *
39
 * @package Elgg.Core
40
 * @subpackage Notifications
41
 */
42
43
/**
44
 * Register a notification event
45
 *
46
 * Elgg sends notifications for the items that have been registered with this
47
 * function. For example, if you want notifications to be sent when a bookmark
48
 * has been created or updated, call the function like this:
49
 *
50
 * 	   elgg_register_notification_event('object', 'bookmarks', array('create', 'update'));
51
 *
52
 * @param string $object_type    'object', 'user', 'group', 'site'
53
 * @param string $object_subtype The subtype or name of the entity
54
 * @param array  $actions        Array of actions or empty array for the action event.
55
 *                                An event is usually described by the first string passed
56
 *                                to elgg_trigger_event(). Examples include
57
 *                                'create', 'update', and 'publish'. The default is 'create'.
58
 * @return void
59
 * @since 1.9
60
 */
61
function elgg_register_notification_event($object_type, $object_subtype, array $actions = []) {
62 31
	_elgg_services()->notifications->registerEvent($object_type, $object_subtype, $actions);
63 31
}
64
65
/**
66
 * Unregister a notification event
67
 *
68
 * @param string $object_type    'object', 'user', 'group', 'site'
69
 * @param string $object_subtype The type of the entity
70
 * @return bool
71
 * @since 1.9
72
 */
73
function elgg_unregister_notification_event($object_type, $object_subtype) {
74
	return _elgg_services()->notifications->unregisterEvent($object_type, $object_subtype);
75
}
76
77
/**
78
 * Register a delivery method for notifications
79
 *
80
 * Register for the 'send', 'notification:[method name]' plugin hook to handle
81
 * sending a notification. A notification object is in the params array for the
82
 * hook with the key 'notification'. See \Elgg\Notifications\Notification.
83
 *
84
 * @param string $name The notification method name
85
 * @return void
86
 * @see elgg_unregister_notification_method()
87
 * @since 1.9
88
 */
89
function elgg_register_notification_method($name) {
90 32
	_elgg_services()->notifications->registerMethod($name);
91 32
}
92
93
/**
94
 * Returns registered delivery methods for notifications
95
 * <code>
96
 *	[
97
 *		'email' => 'email',
98
 *		'sms' => 'sms',
99
 *	]
100
 * </code>
101
 *
102
 * @return array
103
 * @since 2.3
104
 */
105
function elgg_get_notification_methods() {
106
	return _elgg_services()->notifications->getMethods();
107
}
108
109
/**
110
 * Unregister a delivery method for notifications
111
 *
112
 * @param string $name The notification method name
113
 * @return bool
114
 * @see elgg_register_notification_method()
115
 * @since 1.9
116
 */
117
function elgg_unregister_notification_method($name) {
118
	return _elgg_services()->notifications->unregisterMethod($name);
119
}
120
121
/**
122
 * Subscribe a user to notifications about a target entity
123
 *
124
 * @param int    $user_guid   The GUID of the user to subscribe to notifications
125
 * @param string $method      The delivery method of the notifications
126
 * @param int    $target_guid The entity to receive notifications about
127
 * @return bool
128
 * @since 1.9
129
 */
130
function elgg_add_subscription($user_guid, $method, $target_guid) {
131
	$methods = _elgg_services()->notifications->getMethods();
132
	$db = _elgg_services()->db;
133
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
134
	return $subs->addSubscription($user_guid, $method, $target_guid);
135
}
136
137
/**
138
 * Unsubscribe a user to notifications about a target entity
139
 *
140
 * @param int    $user_guid   The GUID of the user to unsubscribe to notifications
141
 * @param string $method      The delivery method of the notifications to stop
142
 * @param int    $target_guid The entity to stop receiving notifications about
143
 * @return bool
144
 * @since 1.9
145
 */
146
function elgg_remove_subscription($user_guid, $method, $target_guid) {
147 2
	$methods = _elgg_services()->notifications->getMethods();
148 2
	$db = _elgg_services()->db;
149 2
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
150 2
	return $subs->removeSubscription($user_guid, $method, $target_guid);
151
}
152
153
/**
154
 * Get the subscriptions for the content created inside this container.
155
 *
156
 * The return array is of the form:
157
 *
158
 * array(
159
 *     <user guid> => array('email', 'sms', 'ajax'),
160
 * );
161
 *
162
 * @param int $container_guid GUID of the entity acting as a container
163
 * @return array User GUIDs (keys) and their subscription types (values).
164
 * @since 1.9
165
 * @todo deprecate once new subscriptions system has been added
166
 */
167
function elgg_get_subscriptions_for_container($container_guid) {
168
	$methods = _elgg_services()->notifications->getMethods();
169
	$db = _elgg_services()->db;
170
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
171
	return $subs->getSubscriptionsForContainer($container_guid);
172
}
173
174
/**
175
 * Queue a notification event for later handling
176
 *
177
 * Checks to see if this event has been registered for notifications.
178
 * If so, it adds the event to a notification queue.
179
 *
180
 * This function triggers the 'enqueue', 'notification' hook.
181
 *
182
 * @param string    $action The name of the action
183
 * @param string    $type   The type of the object
184
 * @param \ElggData $object The object of the event
185
 * @return void
186
 * @access private
187
 * @since 1.9
188
 */
189
function _elgg_enqueue_notification_event($action, $type, $object) {
190 430
	_elgg_services()->notifications->enqueueEvent($action, $type, $object);
191 430
}
192
193
/**
194
 * Process notification queue
195
 *
196
 * @return void
197
 *
198
 * @access private
199
 */
200
function _elgg_notifications_cron() {
201
	// calculate when we should stop
202
	// @todo make configurable?
203
	$stop_time = time() + 45;
204
	_elgg_services()->notifications->processQueue($stop_time);
205
}
206
207
/**
208
 * Send an email notification
209
 *
210
 * @param string $hook   Hook name
211
 * @param string $type   Hook type
212
 * @param bool   $result Has anyone sent a message yet?
213
 * @param array  $params Hook parameters
214
 * @return bool
215
 * @access private
216
 */
217
function _elgg_send_email_notification($hook, $type, $result, $params) {
218
	
219 2
	if ($result === true) {
220
		// assume someone else already sent the message
221
		return;
222
	}
223
224 2
	$message = $params['notification'];
225 2
	if (!$message instanceof \Elgg\Notifications\Notification) {
226
		return false;
227
	}
228
229 2
	$sender = $message->getSender();
230 2
	$recipient = $message->getRecipient();
231
232 2
	if (!$sender) {
233
		return false;
234
	}
235
236 2
	if (!$recipient || !$recipient->email) {
237
		return false;
238
	}
239
240 2
	$email = \Elgg\Email::factory([
241 2
		'from' => $sender,
242 2
		'to' => $recipient,
243 2
		'subject' => $message->subject,
244 2
		'body' => $message->body,
245 2
		'params' => $message->params,
246
	]);
247
248 2
	return _elgg_services()->emails->send($email);
249
}
250
251
/**
252
 * Adds default Message-ID header to all e-mails
253
 *
254
 * @param string      $hook  "prepare"
255
 * @param string      $type  "system:email"
256
 * @param \Elgg\Email $email Email instance
257
 *
258
 * @see    https://tools.ietf.org/html/rfc5322#section-3.6.4
259
 *
260
 * @return array
261
 * @access private
262
 */
263
function _elgg_notifications_smtp_default_message_id_header($hook, $type, $email) {
2 ignored issues
show
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

263
function _elgg_notifications_smtp_default_message_id_header(/** @scrutinizer ignore-unused */ $hook, $type, $email) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

263
function _elgg_notifications_smtp_default_message_id_header($hook, /** @scrutinizer ignore-unused */ $type, $email) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
264
	
265 2
	if (!$email instanceof \Elgg\Email) {
266
		return;
267
	}
268
	
269 2
	$hostname = parse_url(elgg_get_site_url(), PHP_URL_HOST);
270 2
	$url_path = parse_url(elgg_get_site_url(), PHP_URL_PATH);
271
	
272 2
	$mt = microtime(true);
273
	
274 2
	$email->addHeader('Message-ID', "{$url_path}.default.{$mt}@{$hostname}");
275
	
276 2
	return $email;
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return $email returns the type Elgg\Email which is incompatible with the documented return type array.
Loading history...
277
}
278
279
/**
280
 * Adds default thread SMTP headers to group messages correctly.
281
 * Note that it won't be sufficient for some email clients. Ie. Gmail is looking at message subject anyway.
282
 *
283
 * @param string      $hook  "prepare"
284
 * @param string      $type  "system:email"
285
 * @param \Elgg\Email $email Email instance
286
 *
287
 * @return array
288
 * @access private
289
 */
290
function _elgg_notifications_smtp_thread_headers($hook, $type, $email) {
2 ignored issues
show
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

290
function _elgg_notifications_smtp_thread_headers($hook, /** @scrutinizer ignore-unused */ $type, $email) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

290
function _elgg_notifications_smtp_thread_headers(/** @scrutinizer ignore-unused */ $hook, $type, $email) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
291
292 2
	if (!$email instanceof \Elgg\Email) {
293
		return;
294
	}
295
296 2
	$notificationParams = $email->getParams();
297
298 2
	$notification = elgg_extract('notification', $notificationParams);
299 2
	if (!$notification instanceof \Elgg\Notifications\Notification) {
300 2
		return;
301
	}
302
303
	$object = elgg_extract('object', $notification->params);
304
	if (!$object instanceof \ElggEntity) {
305
		return;
306
	}
307
308
	$event = elgg_extract('event', $notification->params);
309
	if (!$event instanceof \Elgg\Notifications\NotificationEvent) {
310
		return;
311
	}
312
313
	$hostname = parse_url(elgg_get_site_url(), PHP_URL_HOST);
314
	$urlPath = parse_url(elgg_get_site_url(), PHP_URL_PATH);
315
316
	if ($event->getAction() === 'create') {
317
		// create event happens once per entity and we need to guarantee message id uniqueness
318
		// and at the same time have thread message id that we don't need to store
319
		$messageId = "{$urlPath}.entity.{$object->guid}@{$hostname}";
320
	} else {
321
		$mt = microtime(true);
322
		$messageId = "{$urlPath}.entity.{$object->guid}[email protected]{$hostname}";
323
	}
324
325
	$email->addHeader("Message-ID", $messageId);
326
327
	// let's just thread comments by default
328
	$container = $object->getContainerEntity();
329
	if ($container instanceof \ElggEntity && $object instanceof \ElggComment) {
330
		$threadMessageId = "<{$urlPath}.entity.{$container->guid}@{$hostname}>";
331
		$email->addHeader('In-Reply-To', $threadMessageId);
332
		$email->addHeader('References', $threadMessageId);
333
	}
334
335
	return $email;
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return $email returns the type Elgg\Email which is incompatible with the documented return type array.
Loading history...
336
}
337
338
/**
339
 * Notification init
340
 *
341
 * @return void
342
 *
343
 * @access private
344
 */
345
function _elgg_notifications_init() {
346 31
	elgg_register_plugin_hook_handler('cron', 'minute', '_elgg_notifications_cron', 100);
347 31
	elgg_register_event_handler('all', 'all', '_elgg_enqueue_notification_event', 700);
348
349
	// add email notifications
350 31
	elgg_register_notification_method('email');
351 31
	elgg_register_plugin_hook_handler('send', 'notification:email', '_elgg_send_email_notification');
352 31
	elgg_register_plugin_hook_handler('prepare', 'system:email', '_elgg_notifications_smtp_default_message_id_header', 1);
353 31
	elgg_register_plugin_hook_handler('prepare', 'system:email', '_elgg_notifications_smtp_thread_headers');
354
355
	// add ability to set personal notification method
356 31
	elgg_extend_view('forms/account/settings', 'core/settings/account/notifications');
357 31
	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_save_notification_user_settings');
358 31
}
359
360
/**
361
 * Notify a user via their preferences.
362
 *
363
 * @param mixed  $to               Either a guid or an array of guid's to notify.
364
 * @param int    $from             GUID of the sender, which may be a user, site or object.
365
 * @param string $subject          Message subject.
366
 * @param string $message          Message body.
367
 * @param array  $params           Misc additional parameters specific to various methods.
368
 * @param mixed  $methods_override A string, or an array of strings specifying the delivery
369
 *                                 methods to use - or leave blank for delivery using the
370
 *                                 user's chosen delivery methods.
371
 *
372
 * @return array Compound array of each delivery user/delivery method's success or failure.
373
 * @access private
374
 */
375
function _elgg_notify_user($to, $from, $subject, $message, array $params = null, $methods_override = "") {
376
377
	$notify_service = _elgg_services()->notifications;
378
379
	// Sanitise
380
	if (!is_array($to)) {
381
		$to = [(int) $to];
382
	}
383
	$from = (int) $from;
384
	//$subject = sanitise_string($subject);
385
	// Get notification methods
386
	if (($methods_override) && (!is_array($methods_override))) {
387
		$methods_override = [$methods_override];
388
	}
389
390
	$result = [];
391
392
	foreach ($to as $guid) {
393
		// Results for a user are...
394
		$result[$guid] = [];
395
396
		$recipient = get_entity($guid);
397
		if (empty($recipient)) {
398
			continue;
399
		}
400
401
		// Are we overriding delivery?
402
		$methods = $methods_override;
403
		if (empty($methods)) {
404
			$methods = [];
405
406
			if (!($recipient instanceof ElggUser)) {
407
				// not sending to a user so can't get user notification settings
408
				continue;
409
			}
410
411
			$tmp = $recipient->getNotificationSettings();
412
			if (empty($tmp)) {
413
				// user has no notification settings
414
				continue;
415
			}
416
417
			foreach ($tmp as $k => $v) {
418
				// Add method if method is turned on for user!
419
				if ($v) {
420
					$methods[] = $k;
421
				}
422
			}
423
		}
424
425
		if (empty($methods)) {
426
			continue;
427
		}
428
429
		// Deliver
430
		foreach ($methods as $method) {
431
			$handler = $notify_service->getDeprecatedHandler($method);
432
			/* @var callable $handler */
433
			if (!$handler || !is_callable($handler)) {
434
				elgg_log("No handler registered for the method $method", 'INFO');
435
				continue;
436
			}
437
438
			elgg_log("Sending message to $guid using $method");
439
440
			// Trigger handler and retrieve result.
441
			try {
442
				$result[$guid][$method] = call_user_func(
443
					$handler,
444
					$from ? get_entity($from) : null,
445
					get_entity($guid),
446
					$subject,
447
					$message,
448
					$params
449
				);
450
			} catch (Exception $e) {
451
				elgg_log($e->getMessage(), 'ERROR');
452
			}
453
		}
454
	}
455
456
	return $result;
457
}
458
459
/**
460
 * Notifications
461
 * This file contains classes and functions which allow plugins to register and send notifications.
462
 *
463
 * There are notification methods which are provided out of the box
464
 * (see notification_init() ). Each method is identified by a string, e.g. "email".
465
 *
466
 * To register an event use register_notification_handler() and pass the method name and a
467
 * handler function.
468
 *
469
 * To send a notification call notify() passing it the method you wish to use combined with a
470
 * number of method specific addressing parameters.
471
 *
472
 * Catch NotificationException to trap errors.
473
 *
474
 * @package Elgg.Core
475
 * @subpackage Notifications
476
 */
477
478
/**
479
 * Notify a user via their preferences.
480
 *
481
 * @param mixed  $to               Either a guid or an array of guid's to notify.
482
 * @param int    $from             GUID of the sender, which may be a user, site or object.
483
 * @param string $subject          Message subject.
484
 * @param string $message          Message body.
485
 * @param array  $params           Misc additional parameters specific to various methods.
486
 *
487
 *                                 By default Elgg core supports three parameters, which give
488
 *                                 notification plugins more control over the notifications:
489
 *
490
 *                                 object => null|\ElggEntity|\ElggAnnotation The object that
491
 *                                           is triggering the notification.
492
 *
493
 *                                 action => null|string Word that describes the action that
494
 *                                           is triggering the notification (e.g. "create"
495
 *                                           or "update").
496
 *
497
 *                                 summary => null|string Summary that notification plugins
498
 *                                            can use alongside the notification title and body.
499
 *
500
 * @param mixed  $methods_override A string, or an array of strings specifying the delivery
501
 *                                 methods to use - or leave blank for delivery using the
502
 *                                 user's chosen delivery methods.
503
 *
504
 * @return array Compound array of each delivery user/delivery method's success or failure.
505
 * @throws NotificationException
506
 */
507
function notify_user($to, $from = 0, $subject = '', $message = '', array $params = [], $methods_override = null) {
508
509 26
	$params['subject'] = $subject;
510 26
	$params['body'] = $message;
511 26
	$params['methods_override'] = $methods_override;
512
513 26
	if ($from) {
514 26
		$sender = get_entity($from);
515
	} else {
516
		$sender = elgg_get_site_entity();
517
	}
518 26
	if (!$sender) {
519
		return [];
520
	}
521
522 26
	$recipients = [];
523 26
	$to = (array) $to;
524 26
	foreach ($to as $guid) {
525 26
		$recipient = get_entity($guid);
526 26
		if (!$recipient) {
527 24
			continue;
528
		}
529 26
		$recipients[] = $recipient;
530
	}
531
532 26
	return _elgg_services()->notifications->sendInstantNotifications($sender, $recipients, $params);
533
}
534
535
/**
536
 * Send an email to any email address
537
 *
538
 * @param \Elgg\Email $email Email
539
 * @return bool
540
 * @since 1.7.2
541
 */
542
function elgg_send_email($email) {
543
544 3
	if (!$email instanceof \Elgg\Email) {
545 3
		elgg_deprecated_notice(__FUNCTION__ . '
546
			 should be given a single instance of \Elgg\Email
547 3
		', '3.0');
548
549 3
		$args = func_get_args();
550 3
		$email = \Elgg\Email::factory([
551 3
			'from' => array_shift($args),
552 3
			'to' => array_shift($args),
553 3
			'subject' => array_shift($args),
554 3
			'body' => array_shift($args),
555 3
			'params' => array_shift($args) ? : [],
556
		]);
557
	}
558
559 3
	return _elgg_services()->emails->send($email);
560
}
561
562
/**
563
 * Replace default email transport
564
 *
565
 * @note If you are replacing the transport persistently, e.g. on each page request via
566
 * a plugin, avoid using plugin settings to store transport configuration, as it
567
 * may be expensive to fetch these settings. Instead, configure the transport
568
 * via elgg-config/settings.php or use site config DB storage.
569
 *
570
 * @param \Zend\Mail\Transport\TransportInterface $mailer Transport
571
 * @return void
572
 */
573
function elgg_set_email_transport(\Zend\Mail\Transport\TransportInterface $mailer) {
574
	_elgg_services()->setValue('mailer', $mailer);
575
}
576
577
/**
578
 * Save personal notification settings - input comes from request
579
 *
580
 * @return void
581
 * @access private
582
 */
583
function _elgg_save_notification_user_settings() {
584
585
	$user = elgg_get_logged_in_user_entity();
586
	if (!$user) {
587
		return;
588
	}
589
590
	$method = get_input('method');
591
592
	$current_settings = $user->getNotificationSettings();
593
594
	$result = false;
595
	foreach ($method as $k => $v) {
596
		// check if setting has changed and skip if not
597
		if ($current_settings[$k] == ($v == 'yes')) {
598
			continue;
599
		}
600
601
		$result = $user->setNotificationSetting($k, ($v == 'yes'));
602
		if (!$result) {
603
			register_error(elgg_echo('notifications:usersettings:save:fail'));
604
		}
605
	}
606
607
	if ($result) {
608
		system_message(elgg_echo('notifications:usersettings:save:ok'));
609
	}
610
}
611
612
/**
613
 * Register unit tests
614
 *
615
 * @param string $hook  'unit_test'
616
 * @param string $type  'system'
617
 * @param array  $tests current return value
618
 *
619
 * @return array
620
 *
621
 * @access private
622
 * @codeCoverageIgnore
623
 */
624
function _elgg_notifications_test($hook, $type, $tests) {
625
	$tests[] = ElggCoreDatabaseQueueTest::class;
626
	return $tests;
627
}
628
629
/**
630
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
631
 */
632
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
633 18
	$events->registerHandler('init', 'system', '_elgg_notifications_init');
634
635 18
	$hooks->registerHandler('unit_test', 'system', '_elgg_notifications_test');
636
};
637