|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* balloon |
|
7
|
|
|
* |
|
8
|
|
|
* @copyright Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com) |
|
9
|
|
|
* @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Balloon\App\Notification; |
|
13
|
|
|
|
|
14
|
|
|
use Balloon\App\Notification\Adapter\AdapterInterface; |
|
15
|
|
|
use Balloon\Filesystem\Node\Collection; |
|
16
|
|
|
use Balloon\Filesystem\Node\NodeInterface; |
|
17
|
|
|
use Balloon\Server; |
|
18
|
|
|
use Balloon\Server\User; |
|
19
|
|
|
use InvalidArgumentException; |
|
20
|
|
|
use MongoDB\BSON\ObjectId; |
|
21
|
|
|
use MongoDB\BSON\UTCDateTime; |
|
22
|
|
|
use MongoDB\Database; |
|
23
|
|
|
use Psr\Log\LoggerInterface; |
|
24
|
|
|
|
|
25
|
|
|
class Notifier |
|
26
|
|
|
{ |
|
27
|
|
|
/** |
|
28
|
|
|
* Notifications. |
|
29
|
|
|
* |
|
30
|
|
|
* @var array |
|
31
|
|
|
*/ |
|
32
|
|
|
protected $notifications = []; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* Adapter. |
|
36
|
|
|
* |
|
37
|
|
|
* @var array |
|
38
|
|
|
*/ |
|
39
|
|
|
protected $adapter = []; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Logger. |
|
43
|
|
|
* |
|
44
|
|
|
* @var LoggerInterface |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $logger; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Database. |
|
50
|
|
|
* |
|
51
|
|
|
* @var Database |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $db; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* Server. |
|
57
|
|
|
* |
|
58
|
|
|
* @var Server |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $server; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* Collection name. |
|
64
|
|
|
* |
|
65
|
|
|
* @var string |
|
66
|
|
|
*/ |
|
67
|
|
|
protected $collection_name = 'notification'; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Constructor. |
|
71
|
|
|
* |
|
72
|
|
|
* @param Database $db |
|
73
|
|
|
* @param Server $server |
|
74
|
|
|
* @param LoggerInterace $logger |
|
75
|
|
|
* @param iterable $config |
|
76
|
|
|
*/ |
|
77
|
|
|
public function __construct(Database $db, Server $server, LoggerInterface $logger, ?Iterable $config = null) |
|
78
|
|
|
{ |
|
79
|
|
|
$this->logger = $logger; |
|
80
|
|
|
$this->db = $db; |
|
81
|
|
|
$this->server = $server; |
|
82
|
|
|
$this->setOptions($config); |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* Set options. |
|
87
|
|
|
* |
|
88
|
|
|
* @param iterable $config |
|
89
|
|
|
* |
|
90
|
|
|
* @return Notifier |
|
91
|
|
|
*/ |
|
92
|
|
|
public function setOptions(?Iterable $config = null): self |
|
93
|
|
|
{ |
|
94
|
|
|
if (null === $config) { |
|
95
|
|
|
return $this; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
foreach ($config as $option => $value) { |
|
99
|
|
|
switch ($option) { |
|
100
|
|
|
case 'adapter': |
|
|
|
|
|
|
101
|
|
|
foreach ($value as $name => $adapter) { |
|
102
|
|
|
$this->injectAdapter($adapter, $name); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
break; |
|
106
|
|
|
default: |
|
107
|
|
|
throw new InvalidArgumentException('invalid option '.$option.' given'); |
|
108
|
|
|
} |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
return $this; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* Send notification. |
|
116
|
|
|
* |
|
117
|
|
|
* @param iterable $receiver |
|
118
|
|
|
* @param User $sender |
|
119
|
|
|
* @param string $subject |
|
|
|
|
|
|
120
|
|
|
* @param string $body |
|
|
|
|
|
|
121
|
|
|
* @param array $context |
|
122
|
|
|
* |
|
123
|
|
|
* @return bool |
|
124
|
|
|
*/ |
|
125
|
|
|
public function notify(Iterable $receiver, ?User $sender, MessageInterface $message, array $context = []): bool |
|
126
|
|
|
{ |
|
127
|
|
|
if (0 === count($this->adapter)) { |
|
128
|
|
|
$this->logger->warning('there are no notification adapter enabled, notification can not be sent', [ |
|
129
|
|
|
'category' => get_class($this), |
|
130
|
|
|
]); |
|
131
|
|
|
|
|
132
|
|
|
return false; |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
foreach ($receiver as $user) { |
|
136
|
|
|
foreach ($this->adapter as $name => $adapter) { |
|
137
|
|
|
$this->logger->debug('send notification to user ['.$user->getId().'] via adapter ['.$name.']', [ |
|
138
|
|
|
'category' => get_class($this), |
|
139
|
|
|
]); |
|
140
|
|
|
|
|
141
|
|
|
$adapter->notify($user, $sender, $message, $context); |
|
142
|
|
|
} |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
return true; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Has adapter. |
|
150
|
|
|
* |
|
151
|
|
|
* @param string $name |
|
152
|
|
|
* |
|
153
|
|
|
* @return bool |
|
154
|
|
|
*/ |
|
155
|
|
|
public function hasAdapter(string $name): bool |
|
156
|
|
|
{ |
|
157
|
|
|
return isset($this->adapter[$name]); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* Inject adapter. |
|
162
|
|
|
* |
|
163
|
|
|
* @param AdapterInterface $adapter |
|
164
|
|
|
* @param string $name |
|
165
|
|
|
* |
|
166
|
|
|
* @return Notifier |
|
167
|
|
|
*/ |
|
168
|
|
|
public function injectAdapter(AdapterInterface $adapter, ?string $name = null): self |
|
169
|
|
|
{ |
|
170
|
|
|
if (null === $name) { |
|
171
|
|
|
$name = get_class($adapter); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
$this->logger->debug('inject notification adapter ['.$name.'] of type ['.get_class($adapter).']', [ |
|
175
|
|
|
'category' => get_class($this), |
|
176
|
|
|
]); |
|
177
|
|
|
|
|
178
|
|
|
if ($this->hasAdapter($name)) { |
|
179
|
|
|
throw new Exception\AdapterNotUnique('adapter '.$name.' is already registered'); |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
$this->adapter[$name] = $adapter; |
|
183
|
|
|
|
|
184
|
|
|
return $this; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Get adapter. |
|
189
|
|
|
* |
|
190
|
|
|
* @param string $name |
|
191
|
|
|
* |
|
192
|
|
|
* @return AdapterInterface |
|
193
|
|
|
*/ |
|
194
|
|
|
public function getAdapter(string $name): AdapterInterface |
|
195
|
|
|
{ |
|
196
|
|
|
if (!$this->hasAdapter($name)) { |
|
197
|
|
|
throw new Exception\AdapterNotFound('adapter '.$name.' is not registered'); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
return $this->adapter[$name]; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* Get adapters. |
|
205
|
|
|
* |
|
206
|
|
|
* @param array $adapters |
|
207
|
|
|
* |
|
208
|
|
|
* @return AdapterInterface[] |
|
209
|
|
|
*/ |
|
210
|
|
|
public function getAdapters(array $adapters = []): array |
|
|
|
|
|
|
211
|
|
|
{ |
|
212
|
|
|
if (empty($adapter)) { |
|
|
|
|
|
|
213
|
|
|
return $this->adapter; |
|
214
|
|
|
} |
|
215
|
|
|
$list = []; |
|
216
|
|
|
foreach ($adapter as $name) { |
|
217
|
|
|
if (!$this->hasAdapter($name)) { |
|
218
|
|
|
throw new Exception\AdapterNotFound('adapter '.$name.' is not registered'); |
|
219
|
|
|
} |
|
220
|
|
|
$list[$name] = $this->adapter[$name]; |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
return $list; |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
/** |
|
227
|
|
|
* Add notification. |
|
228
|
|
|
* |
|
229
|
|
|
* @param array $receiver |
|
230
|
|
|
* @param User $user |
|
|
|
|
|
|
231
|
|
|
* @param MessageInterface $message |
|
232
|
|
|
* @param array $context |
|
233
|
|
|
* |
|
234
|
|
|
* @return ObjectId |
|
235
|
|
|
*/ |
|
236
|
|
|
public function postNotification(User $receiver, ?User $sender, MessageInterface $message, array $context = []): ObjectId |
|
237
|
|
|
{ |
|
238
|
|
|
$data = [ |
|
239
|
|
|
'context' => $context, |
|
240
|
|
|
'subject' => $message->getSubject($receiver), |
|
241
|
|
|
'body' => $message->getBody($receiver), |
|
242
|
|
|
'receiver' => $receiver->getId(), |
|
243
|
|
|
]; |
|
244
|
|
|
|
|
245
|
|
|
if ($sender instanceof User) { |
|
246
|
|
|
$data['sender'] = $sender->getId(); |
|
247
|
|
|
} |
|
248
|
|
|
|
|
249
|
|
|
$result = $this->db->{$this->collection_name}->insertOne($data); |
|
250
|
|
|
|
|
251
|
|
|
return $result->getInsertedId(); |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* Get notifications. |
|
256
|
|
|
* |
|
257
|
|
|
* @param User $user |
|
258
|
|
|
* @param int $offset |
|
259
|
|
|
* @param int $limit |
|
260
|
|
|
* @param int $total |
|
261
|
|
|
* |
|
262
|
|
|
* @return iterable |
|
263
|
|
|
*/ |
|
264
|
|
|
public function getNotifications(User $user, ?int $offset = null, ?int $limit = null, ?int &$total = null): Iterable |
|
265
|
|
|
{ |
|
266
|
|
|
$total = $this->db->{$this->collection_name}->count(['receiver' => $user->getId()]); |
|
267
|
|
|
$result = $this->db->{$this->collection_name}->find(['receiver' => $this->server->getIdentity()->getId()], [ |
|
268
|
|
|
'skip' => $offset, |
|
269
|
|
|
'limit' => $limit, |
|
270
|
|
|
]); |
|
271
|
|
|
|
|
272
|
|
|
return $result; |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
/** |
|
276
|
|
|
* Get notification. |
|
277
|
|
|
* |
|
278
|
|
|
* @param ObjectId $id |
|
279
|
|
|
* |
|
280
|
|
|
* @return array |
|
281
|
|
|
*/ |
|
282
|
|
|
public function getNotification(ObjectId $id): array |
|
283
|
|
|
{ |
|
284
|
|
|
$result = $this->db->{$this->collection_name}->findOne([ |
|
285
|
|
|
'_id' => $id, |
|
286
|
|
|
'receiver' => $this->server->getIdentity()->getId(), |
|
287
|
|
|
]); |
|
288
|
|
|
|
|
289
|
|
|
if ($result === null) { |
|
290
|
|
|
throw new Exception\NotificationNotFound('notification not found'); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
return $result; |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
/** |
|
297
|
|
|
* Get notifications. |
|
298
|
|
|
* |
|
299
|
|
|
* @param ObjectId $id |
|
300
|
|
|
* |
|
301
|
|
|
* @return bool |
|
302
|
|
|
*/ |
|
303
|
|
|
public function deleteNotification(ObjectId $id): bool |
|
304
|
|
|
{ |
|
305
|
|
|
$result = $this->db->{$this->collection_name}->deleteOne([ |
|
306
|
|
|
'_id' => $id, |
|
307
|
|
|
'receiver' => $this->server->getIdentity()->getId(), |
|
308
|
|
|
]); |
|
309
|
|
|
|
|
310
|
|
|
if (null === $result) { |
|
311
|
|
|
throw new Exception\NotificationNotFound('notification not found'); |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
$this->logger->debug('notification ['.$id.'] removed from user ['.$this->server->getIdentity()->getId().']', [ |
|
315
|
|
|
'category' => get_class($this), |
|
316
|
|
|
]); |
|
317
|
|
|
|
|
318
|
|
|
return true; |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
/** |
|
322
|
|
|
* Throttle subscriptions. |
|
323
|
|
|
*/ |
|
324
|
|
|
public function throttleSubscriptions(NodeInterface $node, array $user): Notifier |
|
325
|
|
|
{ |
|
326
|
|
|
$node_id = $node->isReference() ? $node->getShareId() : $node->getId(); |
|
327
|
|
|
$this->db->subscription->updateMany([ |
|
328
|
|
|
'node' => $node_id, |
|
329
|
|
|
'user' => [ |
|
330
|
|
|
'$in' => $user, |
|
331
|
|
|
], |
|
332
|
|
|
], [ |
|
333
|
|
|
'$set' => [ |
|
334
|
|
|
'last_notification' => new UTCDateTime(), |
|
335
|
|
|
], |
|
336
|
|
|
]); |
|
337
|
|
|
|
|
338
|
|
|
return $this; |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
/** |
|
342
|
|
|
* Get subscription. |
|
343
|
|
|
*/ |
|
344
|
|
|
public function getSubscription(NodeInterface $node, User $user): ?array |
|
345
|
|
|
{ |
|
346
|
|
|
$node_id = $node->isReference() ? $node->getShareId() : $node->getId(); |
|
347
|
|
|
|
|
348
|
|
|
return $this->db->subscription->findOne([ |
|
349
|
|
|
'node' => $node_id, |
|
350
|
|
|
'user' => $user->getId(), |
|
351
|
|
|
]); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
/** |
|
355
|
|
|
* Get subscriptions. |
|
356
|
|
|
*/ |
|
357
|
|
|
public function getSubscriptions(NodeInterface $node): Iterable |
|
358
|
|
|
{ |
|
359
|
|
|
$node_id = $node->isReference() ? $node->getShareId() : $node->getId(); |
|
360
|
|
|
|
|
361
|
|
|
return $this->db->subscription->find([ |
|
362
|
|
|
'node' => $node_id, |
|
363
|
|
|
]); |
|
364
|
|
|
} |
|
365
|
|
|
|
|
366
|
|
|
/** |
|
367
|
|
|
* Subscribe to node updates. |
|
368
|
|
|
* |
|
369
|
|
|
* @param NodeInterface $node |
|
370
|
|
|
* @param bool $subscribe |
|
371
|
|
|
* @param bool $exclude_me |
|
372
|
|
|
* @param bool $recursive |
|
373
|
|
|
* |
|
374
|
|
|
* @return bool |
|
375
|
|
|
*/ |
|
376
|
|
|
public function subscribeNode(NodeInterface $node, bool $subscribe = true, bool $exclude_me = true, bool $recursive = false): bool |
|
377
|
|
|
{ |
|
378
|
|
|
$node_id = $node->isReference() ? $node->getShareId() : $node->getId(); |
|
379
|
|
|
$user_id = $this->server->getIdentity()->getId(); |
|
380
|
|
|
|
|
381
|
|
|
if (true === $subscribe) { |
|
382
|
|
|
$this->logger->debug('user ['.$this->server->getIdentity()->getId().'] subscribes node ['.$node->getId().']', [ |
|
383
|
|
|
'category' => get_class($this), |
|
384
|
|
|
]); |
|
385
|
|
|
|
|
386
|
|
|
$subscription = [ |
|
387
|
|
|
'timestamp' => new UTCDateTime(), |
|
388
|
|
|
'exclude_me' => $exclude_me, |
|
389
|
|
|
'recursive' => $recursive, |
|
390
|
|
|
'user' => $user_id, |
|
391
|
|
|
'node' => $node_id, |
|
392
|
|
|
]; |
|
393
|
|
|
|
|
394
|
|
|
$this->db->subscription->replaceOne( |
|
395
|
|
|
[ |
|
396
|
|
|
'user' => $subscription['user'], |
|
397
|
|
|
'node' => $subscription['node'], |
|
398
|
|
|
], |
|
399
|
|
|
$subscription, |
|
400
|
|
|
[ |
|
401
|
|
|
'upsert' => true, |
|
402
|
|
|
] |
|
403
|
|
|
); |
|
404
|
|
|
|
|
405
|
|
|
if ($node instanceof Collection && $recursive === true) { |
|
406
|
|
|
$db = $this->db; |
|
407
|
|
|
$node->doRecursiveAction(function ($child) use ($db, $subscription) { |
|
408
|
|
|
$subscription['node_id'] = $child->getId(); |
|
409
|
|
|
$db->subscription->replaceOne( |
|
410
|
|
|
[ |
|
411
|
|
|
'user' => $subscription['user'], |
|
412
|
|
|
'node' => $subscription['node'], |
|
413
|
|
|
], |
|
414
|
|
|
$subscription, |
|
415
|
|
|
[ |
|
416
|
|
|
'upsert' => true, |
|
417
|
|
|
] |
|
418
|
|
|
); |
|
419
|
|
|
}); |
|
420
|
|
|
} |
|
421
|
|
|
} else { |
|
422
|
|
|
$this->logger->debug('user ['.$this->server->getIdentity()->getId().'] unsubscribes node ['.$node->getId().']', [ |
|
423
|
|
|
'category' => get_class($this), |
|
424
|
|
|
]); |
|
425
|
|
|
|
|
426
|
|
|
$this->db->subscription->deleteOne([ |
|
427
|
|
|
'user' => $user_id, |
|
428
|
|
|
'node' => $node_id, |
|
429
|
|
|
]); |
|
430
|
|
|
|
|
431
|
|
|
if ($node instanceof Collection && $recursive === true) { |
|
432
|
|
|
$db = $this->db; |
|
433
|
|
|
$node->doRecursiveAction(function ($child) use ($db, $node_id, $user_id) { |
|
|
|
|
|
|
434
|
|
|
$db->subscription->deleteOne([ |
|
435
|
|
|
'user' => $user_id, |
|
436
|
|
|
'node' => $node_id, |
|
437
|
|
|
]); |
|
438
|
|
|
}); |
|
439
|
|
|
} |
|
440
|
|
|
} |
|
441
|
|
|
|
|
442
|
|
|
return true; |
|
443
|
|
|
} |
|
444
|
|
|
} |
|
445
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.