Passed
Push — 3.3 ( 43c1f6...839244 )
by Jeroen
35:35 queued 12s
created

_elgg_save_notification_user_settings()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 9
nop 1
dl 0
loc 28
ccs 0
cts 15
cp 0
crap 56
rs 8.8333
c 0
b 0
f 0
1
<?php
2
/**
3
 * Notifications
4
 * This file contains classes and functions which allow plugins to register and send notifications.
5
 *
6
 * There are notification methods which are provided out of the box
7
 * (see notification_init() ). Each method is identified by a string, e.g. "email".
8
 *
9
 * To register an event use register_notification_handler() and pass the method name and a
10
 * handler function.
11
 *
12
 * To send a notification call notify() passing it the method you wish to use combined with a
13
 * number of method specific addressing parameters.
14
 *
15
 * Catch NotificationException to trap errors.
16
 *
17
 * Adding a New Notification Event
18
 * ===============================
19
 * 1. Register the event with elgg_register_notification_event()
20
 *
21
 * 2. Register for the notification message plugin hook:
22
 *    'prepare', 'notification:[event name]'. The event name is of the form
23
 *    [action]:[type]:[subtype]. For example, the publish event for a blog
24
 *    would be named 'publish:object:blog'.
25
 *
26
 *    The parameter array for the plugin hook has the keys 'event', 'method',
27
 *    'recipient', and 'language'. The event is an \Elgg\Notifications\Event
28
 *    object and can provide access to the original object of the event through
29
 *    the method getObject() and the original actor through getActor().
30
 *
31
 *    The plugin hook callback modifies and returns a
32
 *    \Elgg\Notifications\Notification object that holds the message content.
33
 *
34
 *
35
 * Adding a Delivery Method
36
 * =========================
37
 * 1. Register the delivery method name with elgg_register_notification_method()
38
 *
39
 * 2. Register for the plugin hook for sending notifications:
40
 *    'send', 'notification:[method name]'. It receives the notification object
41
 *    of the namespace Elgg\Notifications;
42
 *
43
 *	  class Notification in the params array with the
44
 *    key 'notification'. The callback should return a boolean to indicate whether
45
 *    the message was sent.
46
 *
47
 *
48
 * Subscribing a User for Notifications
49
 * ====================================
50
 * Users subscribe to receive notifications based on container and delivery method.
51
 */
52
53
/**
54
 * Register a notification event
55
 *
56
 * Elgg sends notifications for the items that have been registered with this
57
 * function. For example, if you want notifications to be sent when a bookmark
58
 * has been created or updated, call the function like this:
59
 *
60
 * 	   elgg_register_notification_event('object', 'bookmarks', array('create', 'update'));
61
 *
62
 * @param string $object_type    'object', 'user', 'group', 'site'
63
 * @param string $object_subtype The subtype or name of the entity
64
 * @param array  $actions        Array of actions or empty array for the action event.
65
 *                                An event is usually described by the first string passed
66
 *                                to elgg_trigger_event(). Examples include
67
 *                                'create', 'update', and 'publish'. The default is 'create'.
68
 * @return void
69
 * @since 1.9
70
 */
71
function elgg_register_notification_event($object_type, $object_subtype, array $actions = []) {
72 166
	_elgg_services()->notifications->registerEvent($object_type, $object_subtype, $actions);
73 166
}
74
75
/**
76
 * Unregister a notification event
77
 *
78
 * @param string $object_type    'object', 'user', 'group', 'site'
79
 * @param string $object_subtype The type of the entity
80
 * @param array  $actions        The notification action to unregister, leave empty for all actions
81
 *                                Example ('create', 'delete', 'publish')
82
 *
83
 * @return bool
84
 * @since 1.9
85
 * @see elgg_register_notification_event()
86
 */
87
function elgg_unregister_notification_event($object_type, $object_subtype, array $actions = []) {
88
	return _elgg_services()->notifications->unregisterEvent($object_type, $object_subtype, $actions);
89
}
90
91
/**
92
 * Register a delivery method for notifications
93
 *
94
 * Register for the 'send', 'notification:[method name]' plugin hook to handle
95
 * sending a notification. A notification object is in the params array for the
96
 * hook with the key 'notification'. See \Elgg\Notifications\Notification.
97
 *
98
 * @param string $name The notification method name
99
 * @return void
100
 * @see elgg_unregister_notification_method()
101
 * @since 1.9
102
 */
103
function elgg_register_notification_method($name) {
104 169
	_elgg_services()->notifications->registerMethod($name);
105 169
}
106
107
/**
108
 * Returns registered delivery methods for notifications
109
 * <code>
110
 *	[
111
 *		'email' => 'email',
112
 *		'sms' => 'sms',
113
 *	]
114
 * </code>
115
 *
116
 * @return array
117
 * @since 2.3
118
 */
119
function elgg_get_notification_methods() {
120
	return _elgg_services()->notifications->getMethods();
121
}
122
123
/**
124
 * Unregister a delivery method for notifications
125
 *
126
 * @param string $name The notification method name
127
 * @return bool
128
 * @see elgg_register_notification_method()
129
 * @since 1.9
130
 */
131
function elgg_unregister_notification_method($name) {
132 2
	return _elgg_services()->notifications->unregisterMethod($name);
133
}
134
135
/**
136
 * Subscribe a user to notifications about a target entity
137
 *
138
 * @param int    $user_guid   The GUID of the user to subscribe to notifications
139
 * @param string $method      The delivery method of the notifications
140
 * @param int    $target_guid The entity to receive notifications about
141
 * @return bool
142
 * @since 1.9
143
 */
144
function elgg_add_subscription($user_guid, $method, $target_guid) {
145 2
	$methods = _elgg_services()->notifications->getMethods();
146 2
	$db = _elgg_services()->db;
147 2
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
148 2
	return $subs->addSubscription($user_guid, $method, $target_guid);
149
}
150
151
/**
152
 * Unsubscribe a user to notifications about a target entity
153
 *
154
 * @param int    $user_guid   The GUID of the user to unsubscribe to notifications
155
 * @param string $method      The delivery method of the notifications to stop
156
 * @param int    $target_guid The entity to stop receiving notifications about
157
 * @return bool
158
 * @since 1.9
159
 */
160
function elgg_remove_subscription($user_guid, $method, $target_guid) {
161 4
	$methods = _elgg_services()->notifications->getMethods();
162 4
	$db = _elgg_services()->db;
163 4
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
164 4
	return $subs->removeSubscription($user_guid, $method, $target_guid);
165
}
166
167
/**
168
 * Get the subscriptions for the content created inside this container.
169
 *
170
 * The return array is of the form:
171
 *
172
 * array(
173
 *     <user guid> => array('email', 'sms', 'ajax'),
174
 * );
175
 *
176
 * @param int $container_guid GUID of the entity acting as a container
177
 * @return array User GUIDs (keys) and their subscription types (values).
178
 * @since 1.9
179
 * @todo deprecate once new subscriptions system has been added
180
 */
181
function elgg_get_subscriptions_for_container($container_guid) {
182
	$methods = _elgg_services()->notifications->getMethods();
183
	$db = _elgg_services()->db;
184
	$subs = new \Elgg\Notifications\SubscriptionsService($db, $methods);
185
	return $subs->getSubscriptionsForContainer($container_guid);
186
}
187
188
/**
189
 * Queue a notification event for later handling
190
 *
191
 * Checks to see if this event has been registered for notifications.
192
 * If so, it adds the event to a notification queue.
193
 *
194
 * This function triggers the 'enqueue', 'notification' hook.
195
 *
196
 * @param \Elgg\Event $event 'all', 'all'
197
 *
198
 * @return void
199
 * @internal
200
 * @since 1.9
201
 */
202
function _elgg_enqueue_notification_event(\Elgg\Event $event) {
203 809
	_elgg_services()->notifications->enqueueEvent($event->getName(), $event->getType(), $event->getObject());
204 809
}
205
206
/**
207
 * Process notification queue
208
 *
209
 * @return void
210
 *
211
 * @internal
212
 */
213
function _elgg_notifications_cron() {
214
	// calculate when we should stop
215
	// @todo make configurable?
216
	$stop_time = time() + 45;
217
	_elgg_services()->notifications->processQueue($stop_time);
218
}
219
220
/**
221
 * Send an email notification
222
 *
223
 * @param \Elgg\Hook $hook 'send', 'notification:email'
224
 *
225
 * @return bool
226
 * @internal
227
 */
228
function _elgg_send_email_notification(\Elgg\Hook $hook) {
229
	
230 4
	if ($hook->getValue() === true) {
231
		// assume someone else already sent the message
232
		return;
233
	}
234
235 4
	$message = $hook->getParam('notification');
236 4
	if (!$message instanceof \Elgg\Notifications\Notification) {
237
		return false;
238
	}
239
240 4
	$sender = $message->getSender();
241 4
	$recipient = $message->getRecipient();
242
243 4
	if (!$sender) {
244
		return false;
245
	}
246
247 4
	if (!$recipient || !$recipient->email) {
248
		return false;
249
	}
250
251 4
	$email = \Elgg\Email::factory([
252 4
		'from' => $sender,
253 4
		'to' => $recipient,
254 4
		'subject' => $message->subject,
255 4
		'body' => $message->body,
256 4
		'params' => $message->params,
257
	]);
258
259 4
	return _elgg_services()->emails->send($email);
260
}
261
262
/**
263
 * Adds default Message-ID header to all e-mails
264
 *
265
 * @param \Elgg\Hook $hook "prepare", "system:email"
266
 *
267
 * @see    https://tools.ietf.org/html/rfc5322#section-3.6.4
268
 *
269
 * @return void|\Elgg\Email
270
 * @internal
271
 */
272
function _elgg_notifications_smtp_default_message_id_header(\Elgg\Hook $hook) {
273 4
	$email = $hook->getValue();
274
	
275 4
	if (!$email instanceof \Elgg\Email) {
276
		return;
277
	}
278
	
279 4
	$hostname = parse_url(elgg_get_site_url(), PHP_URL_HOST);
280 4
	$url_path = parse_url(elgg_get_site_url(), PHP_URL_PATH);
281
	
282 4
	$mt = microtime(true);
283
	
284 4
	$email->addHeader('Message-ID', "{$url_path}.default.{$mt}@{$hostname}");
285
	
286 4
	return $email;
287
}
288
289
/**
290
 * Adds default thread SMTP headers to group messages correctly.
291
 * Note that it won't be sufficient for some email clients. Ie. Gmail is looking at message subject anyway.
292
 *
293
 * @param \Elgg\Hook $hook "prepare", "system:email"
294
 *
295
 * @return void|\Elgg\Email
296
 * @internal
297
 */
298
function _elgg_notifications_smtp_thread_headers(\Elgg\Hook $hook) {
299 4
	$email = $hook->getValue();
300 4
	if (!$email instanceof \Elgg\Email) {
301
		return;
302
	}
303
304 4
	$notificationParams = $email->getParams();
305
306 4
	$notification = elgg_extract('notification', $notificationParams);
307 4
	if (!$notification instanceof \Elgg\Notifications\Notification) {
308 4
		return;
309
	}
310
311
	$object = elgg_extract('object', $notification->params);
312
	if (!$object instanceof \ElggEntity) {
313
		return;
314
	}
315
316
	$event = elgg_extract('event', $notification->params);
317
	if (!$event instanceof \Elgg\Notifications\NotificationEvent) {
318
		return;
319
	}
320
321
	$hostname = parse_url(elgg_get_site_url(), PHP_URL_HOST);
322
	$urlPath = parse_url(elgg_get_site_url(), PHP_URL_PATH);
323
324
	if ($event->getAction() === 'create') {
325
		// create event happens once per entity and we need to guarantee message id uniqueness
326
		// and at the same time have thread message id that we don't need to store
327
		$messageId = "{$urlPath}.entity.{$object->guid}@{$hostname}";
328
	} else {
329
		$mt = microtime(true);
330
		$messageId = "{$urlPath}.entity.{$object->guid}.$mt@{$hostname}";
331
	}
332
333
	$email->addHeader("Message-ID", $messageId);
334
335
	// let's just thread comments by default
336
	$container = $object->getContainerEntity();
337
	if ($container instanceof \ElggEntity && $object instanceof \ElggComment) {
338
		$threadMessageId = "<{$urlPath}.entity.{$container->guid}@{$hostname}>";
339
		$email->addHeader('In-Reply-To', $threadMessageId);
340
		$email->addHeader('References', $threadMessageId);
341
	}
342
343
	return $email;
344
}
345
346
/**
347
 * Notification init
348
 *
349
 * @return void
350
 *
351
 * @internal
352
 */
353
function _elgg_notifications_init() {
354 166
	elgg_register_plugin_hook_handler('cron', 'minute', '_elgg_notifications_cron', 100);
355 166
	elgg_register_event_handler('all', 'all', '_elgg_enqueue_notification_event', 700);
356
357
	// add email notifications
358 166
	elgg_register_notification_method('email');
359 166
	elgg_register_plugin_hook_handler('send', 'notification:email', '_elgg_send_email_notification');
360 166
	elgg_register_plugin_hook_handler('prepare', 'system:email', '_elgg_notifications_smtp_default_message_id_header', 1);
361 166
	elgg_register_plugin_hook_handler('prepare', 'system:email', '_elgg_notifications_smtp_thread_headers');
362
363
	// add ability to set personal notification method
364 166
	elgg_extend_view('forms/usersettings/save', 'core/settings/account/notifications');
365 166
	elgg_register_plugin_hook_handler('usersettings:save', 'user', '_elgg_save_notification_user_settings');
366 166
}
367
368
/**
369
 * Notify a user via their preferences.
370
 *
371
 * @param mixed  $to               Either a guid or an array of guid's to notify.
372
 * @param int    $from             GUID of the sender, which may be a user, site or object.
373
 * @param string $subject          Message subject.
374
 * @param string $message          Message body.
375
 * @param array  $params           Misc additional parameters specific to various methods.
376
 * @param mixed  $methods_override A string, or an array of strings specifying the delivery
377
 *                                 methods to use - or leave blank for delivery using the
378
 *                                 user's chosen delivery methods.
379
 *
380
 * @return array Compound array of each delivery user/delivery method's success or failure.
381
 * @internal
382
 */
383
function _elgg_notify_user($to, $from, $subject, $message, array $params = null, $methods_override = "") {
384
385 1
	$notify_service = _elgg_services()->notifications;
386
387
	// Sanitise
388 1
	if (!is_array($to)) {
389 1
		$to = [(int) $to];
390
	}
391 1
	$from = (int) $from;
392
	//$subject = sanitise_string($subject);
393
	// Get notification methods
394 1
	if (($methods_override) && (!is_array($methods_override))) {
395
		$methods_override = [$methods_override];
396
	}
397
398 1
	$result = [];
399
400 1
	foreach ($to as $guid) {
401
		// Results for a user are...
402 1
		$result[$guid] = [];
403
404 1
		$recipient = get_entity($guid);
405 1
		if (empty($recipient)) {
406
			continue;
407
		}
408
409
		// Are we overriding delivery?
410 1
		$methods = $methods_override;
411 1
		if (empty($methods)) {
412
			$methods = [];
413
414
			if (!($recipient instanceof ElggUser)) {
415
				// not sending to a user so can't get user notification settings
416
				continue;
417
			}
418
419
			$tmp = $recipient->getNotificationSettings();
420
			if (empty($tmp)) {
421
				// user has no notification settings
422
				continue;
423
			}
424
425
			foreach ($tmp as $k => $v) {
426
				// Add method if method is turned on for user!
427
				if ($v) {
428
					$methods[] = $k;
429
				}
430
			}
431
		}
432
433 1
		if (empty($methods)) {
434
			continue;
435
		}
436
437
		// Deliver
438 1
		foreach ($methods as $method) {
439 1
			$handler = $notify_service->getDeprecatedHandler($method);
440
			/* @var callable $handler */
441 1
			if (!$handler || !is_callable($handler)) {
442 1
				elgg_log("No handler registered for the method $method", 'INFO');
443 1
				continue;
444
			}
445
446
			elgg_log("Sending message to $guid using $method");
447
448
			// Trigger handler and retrieve result.
449
			try {
450
				$result[$guid][$method] = call_user_func(
451
					$handler,
452
					$from ? get_entity($from) : null,
453
					get_entity($guid),
454
					$subject,
455
					$message,
456
					$params
457
				);
458
			} catch (Exception $e) {
459
				elgg_log($e, 'ERROR');
460
			}
461
		}
462
	}
463
464 1
	return $result;
465
}
466
467
/**
468
 * Notify a user via their preferences.
469
 *
470
 * @param mixed  $to               Either a guid or an array of guid's to notify.
471
 * @param int    $from             GUID of the sender, which may be a user, site or object.
472
 * @param string $subject          Message subject.
473
 * @param string $message          Message body.
474
 * @param array  $params           Misc additional parameters specific to various methods.
475
 *
476
 *                                 By default Elgg core supports three parameters, which give
477
 *                                 notification plugins more control over the notifications:
478
 *
479
 *                                 object => null|\ElggEntity|\ElggAnnotation The object that
480
 *                                           is triggering the notification.
481
 *
482
 *                                 action => null|string Word that describes the action that
483
 *                                           is triggering the notification (e.g. "create"
484
 *                                           or "update").
485
 *
486
 *                                 summary => null|string Summary that notification plugins
487
 *                                            can use alongside the notification title and body.
488
 *
489
 * @param mixed  $methods_override A string, or an array of strings specifying the delivery
490
 *                                 methods to use - or leave blank for delivery using the
491
 *                                 user's chosen delivery methods.
492
 *
493
 * @return array Compound array of each delivery user/delivery method's success or failure.
494
 * @throws NotificationException
495
 */
496
function notify_user($to, $from = 0, $subject = '', $message = '', array $params = [], $methods_override = null) {
497
498 30
	$params['subject'] = $subject;
499 30
	$params['body'] = $message;
500 30
	$params['methods_override'] = $methods_override;
501
502 30
	if ($from) {
503 30
		$sender = get_entity($from);
504
	} else {
505
		$sender = elgg_get_site_entity();
506
	}
507 30
	if (!$sender) {
508
		return [];
509
	}
510
511 30
	$recipients = [];
512 30
	$to = (array) $to;
513 30
	foreach ($to as $guid) {
514 30
		$recipient = get_entity($guid);
515 30
		if (!$recipient) {
516 24
			continue;
517
		}
518 30
		$recipients[] = $recipient;
519
	}
520
521 30
	return _elgg_services()->notifications->sendInstantNotifications($sender, $recipients, $params);
522
}
523
524
/**
525
 * Send an email to any email address
526
 *
527
 * @param \Elgg\Email $email Email
528
 * @return bool
529
 * @since 1.7.2
530
 */
531
function elgg_send_email($email) {
532
533 3
	if (!$email instanceof \Elgg\Email) {
534 3
		elgg_deprecated_notice(__FUNCTION__ . '
535
			 should be given a single instance of \Elgg\Email
536 3
		', '3.0');
537
538 3
		$args = func_get_args();
539 3
		$email = \Elgg\Email::factory([
540 3
			'from' => array_shift($args),
541 3
			'to' => array_shift($args),
542 3
			'subject' => array_shift($args),
543 3
			'body' => array_shift($args),
544 3
			'params' => array_shift($args) ? : [],
545
		]);
546
	}
547
548 3
	return _elgg_services()->emails->send($email);
549
}
550
551
/**
552
 * Replace default email transport
553
 *
554
 * @note If you are replacing the transport persistently, e.g. on each page request via
555
 * a plugin, avoid using plugin settings to store transport configuration, as it
556
 * may be expensive to fetch these settings. Instead, configure the transport
557
 * via elgg-config/settings.php or use site config DB storage.
558
 *
559
 * @param \Zend\Mail\Transport\TransportInterface $mailer Transport
560
 * @return void
561
 */
562
function elgg_set_email_transport(\Zend\Mail\Transport\TransportInterface $mailer) {
563 1
	_elgg_services()->setValue('mailer', $mailer);
564 1
}
565
566
/**
567
 * Save personal notification settings - input comes from request
568
 *
569
 * @param \Elgg\Hook $hook 'usersettings:save', 'user'
570
 *
571
 * @return void
572
 * @internal
573
 */
574
function _elgg_save_notification_user_settings(\Elgg\Hook $hook) {
575
576
	$user = $hook->getUserParam();
577
	$request = $hook->getParam('request');
578
579
	if (!$user instanceof ElggUser || !$request instanceof \Elgg\Request) {
580
		return;
581
	}
582
583
	$method = $request->getParam('method');
584
585
	$current_settings = $user->getNotificationSettings();
586
587
	$result = false;
588
	foreach ($method as $key => $value) {
589
		// check if setting has changed and skip if not
590
		if ($current_settings[$key] === ($value === 'yes')) {
591
			continue;
592
		}
593
594
		$result = $user->setNotificationSetting($key, ($value === 'yes'));
595
		if (!$result) {
596
			$request->validation()->fail('notification_method', '', elgg_echo('notifications:usersettings:save:fail'));
597
		}
598
	}
599
600
	if ($result) {
601
		$request->validation()->pass('notification_method', '', elgg_echo('notifications:usersettings:save:ok'));
602
	}
603
}
604
605 155
/**
606 155
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
607
 */
608
return function(\Elgg\EventsService $events) {
609
	$events->registerHandler('init', 'system', '_elgg_notifications_init');
610
};
611