1
|
|
|
<?php |
2
|
|
|
namespace Elgg\Notifications; |
3
|
|
|
|
4
|
|
|
use ElggEntity; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
8
|
|
|
* |
9
|
|
|
* @access private |
10
|
|
|
* |
11
|
|
|
* @package Elgg.Core |
12
|
|
|
* @subpackage Notifications |
13
|
|
|
* @since 1.9.0 |
14
|
|
|
*/ |
15
|
|
|
class NotificationsService { |
16
|
|
|
|
17
|
|
|
const QUEUE_NAME = 'notifications'; |
18
|
|
|
|
19
|
|
|
/** @var \Elgg\Notifications\SubscriptionsService */ |
20
|
|
|
protected $subscriptions; |
21
|
|
|
|
22
|
|
|
/** @var \Elgg\Queue\Queue */ |
23
|
|
|
protected $queue; |
24
|
|
|
|
25
|
|
|
/** @var \Elgg\PluginHooksService */ |
26
|
|
|
protected $hooks; |
27
|
|
|
|
28
|
|
|
/** @var \ElggSession */ |
29
|
|
|
protected $session; |
30
|
|
|
|
31
|
|
|
/** @var array Registered notification events */ |
32
|
|
|
protected $events = array(); |
33
|
|
|
|
34
|
|
|
/** @var array Registered notification methods */ |
35
|
|
|
protected $methods = array(); |
36
|
|
|
|
37
|
|
|
/** @var array Deprecated notification handlers */ |
38
|
|
|
protected $deprHandlers = array(); |
39
|
|
|
|
40
|
|
|
/** @var array Deprecated message subjects */ |
41
|
|
|
protected $deprSubjects = array(); |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Constructor |
45
|
|
|
* |
46
|
|
|
* @param \Elgg\Notifications\SubscriptionsService $subscriptions Subscription service |
47
|
|
|
* @param \Elgg\Queue\Queue $queue Queue |
48
|
|
|
* @param \Elgg\PluginHooksService $hooks Plugin hook service |
49
|
|
|
* @param \ElggSession $session Session service |
50
|
|
|
*/ |
51
|
10 |
|
public function __construct(\Elgg\Notifications\SubscriptionsService $subscriptions, |
52
|
|
|
\Elgg\Queue\Queue $queue, \Elgg\PluginHooksService $hooks, \ElggSession $session) { |
53
|
10 |
|
$this->subscriptions = $subscriptions; |
54
|
10 |
|
$this->queue = $queue; |
55
|
10 |
|
$this->hooks = $hooks; |
56
|
10 |
|
$this->session = $session; |
57
|
10 |
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @see elgg_register_notification_event() |
61
|
|
|
* @access private |
62
|
|
|
*/ |
63
|
7 |
|
public function registerEvent($type, $subtype, array $actions = array()) { |
64
|
|
|
|
65
|
7 |
|
if (!isset($this->events[$type])) { |
66
|
7 |
|
$this->events[$type] = array(); |
67
|
7 |
|
} |
68
|
7 |
|
if (!isset($this->events[$type][$subtype])) { |
69
|
7 |
|
$this->events[$type][$subtype] = array(); |
70
|
7 |
|
} |
71
|
|
|
|
72
|
7 |
|
$action_list =& $this->events[$type][$subtype]; |
73
|
7 |
|
if ($actions) { |
|
|
|
|
74
|
1 |
|
$action_list = array_unique(array_merge($action_list, $actions)); |
75
|
7 |
|
} elseif (!in_array('create', $action_list)) { |
76
|
7 |
|
$action_list[] = 'create'; |
77
|
7 |
|
} |
78
|
7 |
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @see elgg_unregister_notification_event() |
82
|
|
|
* @access private |
83
|
|
|
*/ |
84
|
1 |
|
public function unregisterEvent($type, $subtype) { |
85
|
|
|
|
86
|
1 |
|
if (!isset($this->events[$type]) || !isset($this->events[$type][$subtype])) { |
87
|
1 |
|
return false; |
88
|
|
|
} |
89
|
|
|
|
90
|
1 |
|
unset($this->events[$type][$subtype]); |
91
|
|
|
|
92
|
1 |
|
return true; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @access private |
97
|
|
|
*/ |
98
|
2 |
|
public function getEvents() { |
99
|
2 |
|
return $this->events; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @see elgg_register_notification_method() |
104
|
|
|
* @access private |
105
|
|
|
*/ |
106
|
2 |
|
public function registerMethod($name) { |
107
|
2 |
|
$this->methods[$name] = $name; |
108
|
2 |
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @see elgg_unregister_notification_method() |
112
|
|
|
* @access private |
113
|
|
|
*/ |
114
|
1 |
|
public function unregisterMethod($name) { |
115
|
1 |
|
if (isset($this->methods[$name])) { |
116
|
1 |
|
unset($this->methods[$name]); |
117
|
1 |
|
return true; |
118
|
|
|
} |
119
|
1 |
|
return false; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @access private |
124
|
|
|
*/ |
125
|
2 |
|
public function getMethods() { |
126
|
2 |
|
return $this->methods; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Add a notification event to the queue |
131
|
|
|
* |
132
|
|
|
* @param string $action Action name |
133
|
|
|
* @param string $type Type of the object of the action |
134
|
|
|
* @param \ElggData $object The object of the action |
135
|
|
|
* @return void |
136
|
|
|
* @access private |
137
|
|
|
*/ |
138
|
5 |
|
public function enqueueEvent($action, $type, $object) { |
139
|
5 |
|
if ($object instanceof \ElggData) { |
140
|
5 |
|
$object_type = $object->getType(); |
141
|
5 |
|
$object_subtype = $object->getSubtype(); |
142
|
|
|
|
143
|
5 |
|
$registered = false; |
144
|
5 |
|
if (isset($this->events[$object_type]) |
145
|
5 |
|
&& isset($this->events[$object_type][$object_subtype]) |
146
|
5 |
|
&& in_array($action, $this->events[$object_type][$object_subtype])) { |
147
|
5 |
|
$registered = true; |
148
|
5 |
|
} |
149
|
|
|
|
150
|
5 |
|
if ($registered) { |
151
|
|
|
$params = array( |
152
|
5 |
|
'action' => $action, |
153
|
5 |
|
'object' => $object, |
154
|
5 |
|
); |
155
|
5 |
|
$registered = $this->hooks->trigger('enqueue', 'notification', $params, $registered); |
156
|
5 |
|
} |
157
|
|
|
|
158
|
5 |
|
if ($registered) { |
159
|
3 |
|
$this->queue->enqueue(new \Elgg\Notifications\Event($object, $action)); |
160
|
3 |
|
} |
161
|
5 |
|
} |
162
|
5 |
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Pull notification events from queue until stop time is reached |
166
|
|
|
* |
167
|
|
|
* @param int $stopTime The Unix time to stop sending notifications |
168
|
|
|
* @return int The number of notification events handled |
169
|
|
|
* @access private |
170
|
|
|
*/ |
171
|
3 |
|
public function processQueue($stopTime) { |
172
|
|
|
|
173
|
3 |
|
$this->subscriptions->methods = $this->methods; |
174
|
|
|
|
175
|
3 |
|
$count = 0; |
176
|
|
|
|
177
|
|
|
// @todo grab mutex |
178
|
|
|
|
179
|
3 |
|
$ia = $this->session->setIgnoreAccess(true); |
180
|
|
|
|
181
|
3 |
|
while (time() < $stopTime) { |
182
|
|
|
// dequeue notification event |
183
|
2 |
|
$event = $this->queue->dequeue(); |
184
|
2 |
|
if (!$event) { |
185
|
2 |
|
break; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
// test for usage of the deprecated override hook |
189
|
1 |
|
if ($this->existsDeprecatedNotificationOverride($event)) { |
190
|
|
|
continue; |
191
|
|
|
} |
192
|
|
|
|
193
|
1 |
|
$subscriptions = $this->subscriptions->getSubscriptions($event); |
194
|
|
|
|
195
|
|
|
// return false to stop the default notification sender |
196
|
1 |
|
$params = array('event' => $event, 'subscriptions' => $subscriptions); |
197
|
1 |
|
if ($this->hooks->trigger('send:before', 'notifications', $params, true)) { |
198
|
1 |
|
$this->sendNotifications($event, $subscriptions); |
199
|
1 |
|
} |
200
|
1 |
|
$this->hooks->trigger('send:after', 'notifications', $params); |
201
|
1 |
|
$count++; |
202
|
1 |
|
} |
203
|
|
|
|
204
|
|
|
// release mutex |
205
|
|
|
|
206
|
3 |
|
$this->session->setIgnoreAccess($ia); |
207
|
|
|
|
208
|
3 |
|
return $count; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Sends the notifications based on subscriptions |
213
|
|
|
* |
214
|
|
|
* @param \Elgg\Notifications\Event $event Notification event |
215
|
|
|
* @param array $subscriptions Subscriptions for this event |
216
|
|
|
* @return int The number of notifications handled |
217
|
|
|
* @access private |
218
|
|
|
*/ |
219
|
1 |
|
protected function sendNotifications($event, $subscriptions) { |
220
|
|
|
|
221
|
1 |
|
if (!$this->methods) { |
|
|
|
|
222
|
1 |
|
return 0; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$count = 0; |
226
|
|
|
foreach ($subscriptions as $guid => $methods) { |
227
|
|
|
foreach ($methods as $method) { |
228
|
|
|
if (in_array($method, $this->methods)) { |
229
|
|
|
if ($this->sendNotification($event, $guid, $method)) { |
230
|
|
|
$count++; |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
return $count; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Send a notification to a subscriber |
240
|
|
|
* |
241
|
|
|
* @param \Elgg\Notifications\Event $event The notification event |
242
|
|
|
* @param int $guid The guid of the subscriber |
243
|
|
|
* @param string $method The notification method |
244
|
|
|
* @return bool |
245
|
|
|
* @access private |
246
|
|
|
*/ |
247
|
3 |
|
protected function sendNotification(\Elgg\Notifications\Event $event, $guid, $method) { |
248
|
|
|
|
249
|
|
|
$recipient = get_user($guid); |
250
|
|
|
if (!$recipient || $recipient->isBanned()) { |
251
|
|
|
return false; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// don't notify the creator of the content |
255
|
|
|
if ($recipient->getGUID() == $event->getActorGUID()) { |
256
|
|
|
return false; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$actor = $event->getActor(); |
260
|
|
|
$object = $event->getObject(); |
261
|
|
|
if (!$actor || !$object) { |
262
|
|
|
return false; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
if (($object instanceof ElggEntity) && !has_access_to_entity($object, $recipient)) { |
|
|
|
|
266
|
|
|
return false; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
$language = $recipient->language; |
270
|
|
|
$params = array( |
271
|
|
|
'event' => $event, |
272
|
|
|
'method' => $method, |
273
|
|
|
'recipient' => $recipient, |
274
|
|
|
'language' => $language, |
275
|
|
|
'object' => $object, |
276
|
|
|
); |
277
|
|
|
|
278
|
|
|
$subject = _elgg_services()->translator->translate('notification:subject', array($actor->name), $language); |
279
|
3 |
|
$body = _elgg_services()->translator->translate('notification:body', array($object->getURL()), $language); |
280
|
|
|
$notification = new \Elgg\Notifications\Notification($event->getActor(), $recipient, $language, $subject, $body, '', $params); |
281
|
|
|
|
282
|
|
|
$type = 'notification:' . $event->getDescription(); |
283
|
|
|
if ($this->hooks->hasHandler('prepare', $type)) { |
284
|
|
|
$notification = $this->hooks->trigger('prepare', $type, $params, $notification); |
285
|
|
|
} else { |
286
|
|
|
// pre Elgg 1.9 notification message generation |
287
|
|
|
$notification = $this->getDeprecatedNotificationBody($notification, $event, $method); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
if ($this->hooks->hasHandler('send', "notification:$method")) { |
291
|
|
|
// return true to indicate the notification has been sent |
292
|
|
|
$params = array( |
293
|
|
|
'notification' => $notification, |
294
|
|
|
'event' => $event, |
295
|
|
|
); |
296
|
|
|
return $this->hooks->trigger('send', "notification:$method", $params, false); |
297
|
|
|
} else { |
298
|
|
|
// pre Elgg 1.9 notification handler |
299
|
|
|
$userGuid = $notification->getRecipientGUID(); |
300
|
|
|
$senderGuid = $notification->getSenderGUID(); |
301
|
|
|
$subject = $notification->subject; |
302
|
|
|
$body = $notification->body; |
303
|
|
|
$params = $notification->params; |
304
|
|
|
return (bool)_elgg_notify_user($userGuid, $senderGuid, $subject, $body, $params, array($method)); |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Register a deprecated notification handler |
310
|
|
|
* |
311
|
|
|
* @param string $method Method name |
312
|
|
|
* @param string $handler Handler callback |
313
|
|
|
* @return void |
314
|
|
|
*/ |
315
|
|
|
public function registerDeprecatedHandler($method, $handler) { |
316
|
|
|
$this->deprHandlers[$method] = $handler; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Get a deprecated notification handler callback |
321
|
|
|
* |
322
|
|
|
* @param string $method Method name |
323
|
|
|
* @return callback|null |
324
|
|
|
*/ |
325
|
|
|
public function getDeprecatedHandler($method) { |
326
|
|
|
if (isset($this->deprHandlers[$method])) { |
327
|
|
|
return $this->deprHandlers[$method]; |
328
|
|
|
} else { |
329
|
|
|
return null; |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Provides a way to incrementally wean Elgg's notifications code from the |
335
|
|
|
* global $NOTIFICATION_HANDLERS |
336
|
|
|
* |
337
|
|
|
* @return array |
338
|
|
|
*/ |
339
|
|
|
public function getMethodsAsDeprecatedGlobal() { |
340
|
|
|
$data = array(); |
341
|
|
|
foreach ($this->methods as $method) { |
342
|
|
|
$data[$method] = 'empty'; |
343
|
|
|
} |
344
|
|
|
return $data; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Get the notification body using a pre-Elgg 1.9 plugin hook |
349
|
|
|
* |
350
|
|
|
* @param \Elgg\Notifications\Notification $notification Notification |
351
|
|
|
* @param \Elgg\Notifications\Event $event Event |
352
|
|
|
* @param string $method Method |
353
|
|
|
* @return \Elgg\Notifications\Notification |
354
|
|
|
*/ |
355
|
|
|
protected function getDeprecatedNotificationBody(\Elgg\Notifications\Notification $notification, \Elgg\Notifications\Event $event, $method) { |
356
|
|
|
$entity = $event->getObject(); |
357
|
|
|
$params = array( |
358
|
|
|
'entity' => $entity, |
359
|
|
|
'to_entity' => $notification->getRecipient(), |
360
|
|
|
'method' => $method, |
361
|
|
|
); |
362
|
|
|
$subject = $this->getDeprecatedNotificationSubject($entity->getType(), $entity->getSubtype()); |
363
|
|
|
$string = $subject . ": " . $entity->getURL(); |
364
|
|
|
$body = $this->hooks->trigger('notify:entity:message', $entity->getType(), $params, $string); |
365
|
|
|
|
366
|
|
|
if ($subject) { |
367
|
|
|
$notification->subject = $subject; |
368
|
|
|
$notification->body = $body; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return $notification; |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Set message subject for deprecated notification code |
376
|
|
|
* |
377
|
|
|
* @param string $type Entity type |
378
|
|
|
* @param string $subtype Entity subtype |
379
|
|
|
* @param string $subject Subject line |
380
|
|
|
* @return void |
381
|
|
|
*/ |
382
|
|
|
public function setDeprecatedNotificationSubject($type, $subtype, $subject) { |
383
|
|
|
if ($type == '') { |
384
|
|
|
$type = '__BLANK__'; |
385
|
|
|
} |
386
|
|
|
if ($subtype == '') { |
387
|
|
|
$subtype = '__BLANK__'; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
if (!isset($this->deprSubjects[$type])) { |
391
|
|
|
$this->deprSubjects[$type] = array(); |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
$this->deprSubjects[$type][$subtype] = $subject; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Get the deprecated subject |
399
|
|
|
* |
400
|
|
|
* @param string $type Entity type |
401
|
|
|
* @param string $subtype Entity subtype |
402
|
|
|
* @return string |
403
|
|
|
*/ |
404
|
|
|
protected function getDeprecatedNotificationSubject($type, $subtype) { |
405
|
|
|
if ($type == '') { |
406
|
|
|
$type = '__BLANK__'; |
407
|
|
|
} |
408
|
|
|
if ($subtype == '') { |
409
|
|
|
$subtype = '__BLANK__'; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
if (!isset($this->deprSubjects[$type])) { |
413
|
|
|
return ''; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
if (!isset($this->deprSubjects[$type][$subtype])) { |
417
|
|
|
return ''; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
return $this->deprSubjects[$type][$subtype]; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Is someone using the deprecated override |
425
|
|
|
* |
426
|
|
|
* @param \Elgg\Notifications\Event $event Event |
427
|
|
|
* @return boolean |
428
|
|
|
*/ |
429
|
1 |
|
protected function existsDeprecatedNotificationOverride(\Elgg\Notifications\Event $event) { |
430
|
1 |
|
$entity = $event->getObject(); |
431
|
1 |
|
if (!elgg_instanceof($entity)) { |
432
|
1 |
|
return false; |
433
|
|
|
} |
434
|
|
|
$params = array( |
435
|
|
|
'event' => $event->getAction(), |
436
|
|
|
'object_type' => $entity->getType(), |
437
|
|
|
'object' => $entity, |
438
|
|
|
); |
439
|
|
|
$hookresult = $this->hooks->trigger('object:notifications', $entity->getType(), $params, false); |
440
|
|
|
if ($hookresult === true) { |
441
|
|
|
elgg_deprecated_notice("Using the plugin hook 'object:notifications' has been deprecated by the hook 'send:before', 'notifications'", 1.9); |
442
|
|
|
return true; |
443
|
|
|
} else { |
444
|
|
|
return false; |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.