Completed
Push — master ( 44e430...fa5669 )
by Raffael
11:31
created

Notifier::postNotification()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 0
cts 14
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
crap 6
eloc 10
nc 2
nop 4
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':
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $subject. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
120
     * @param string   $body
0 ignored issues
show
Bug introduced by
There is no parameter named $body. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
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
0 ignored issues
show
Unused Code introduced by
The parameter $adapters is not used and could be removed.

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

Loading history...
211
    {
212
        if (empty($adapter)) {
0 ignored issues
show
Bug introduced by
The variable $adapter does not exist. Did you mean $adapters?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $user. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
The parameter $child is not used and could be removed.

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

Loading history...
434
                    $db->subscription->deleteOne([
435
                        'user' => $user_id,
436
                        'node' => $node_id,
437
                    ]);
438
                });
439
            }
440
        }
441
442
        return true;
443
    }
444
}
445