Passed
Push — master ( a9f547...49cca7 )
by Raffael
04:18
created

Notifier   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 37
lcom 2
dl 0
loc 365
ccs 0
cts 211
cp 0
rs 8.6
c 0
b 0
f 0
cbo 9

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A customMessage() 0 4 1
A nodeMessage() 0 4 1
B notify() 0 22 4
A hasAdapter() 0 4 1
A injectAdapter() 0 18 3
A getAdapter() 0 8 2
A getAdapters() 0 15 4
A postNotification() 0 17 2
A getNotifications() 0 10 1
A getNotification() 0 13 2
A deleteNotification() 0 17 2
A throttleSubscriptions() 0 16 2
A getSubscription() 0 9 2
A getSubscriptions() 0 8 2
C subscribeNode() 0 68 7
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 MongoDB\BSON\ObjectId;
20
use MongoDB\BSON\UTCDateTime;
21
use MongoDB\Database;
22
use Psr\Log\LoggerInterface;
23
24
class Notifier
25
{
26
    /**
27
     * Notifications.
28
     *
29
     * @var array
30
     */
31
    protected $notifications = [];
32
33
    /**
34
     * Adapter.
35
     *
36
     * @var array
37
     */
38
    protected $adapter = [];
39
40
    /**
41
     * Logger.
42
     *
43
     * @var LoggerInterface
44
     */
45
    protected $logger;
46
47
    /**
48
     * Database.
49
     *
50
     * @var Database
51
     */
52
    protected $db;
53
54
    /**
55
     * Server.
56
     *
57
     * @var Server
58
     */
59
    protected $server;
60
61
    /**
62
     * Collection name.
63
     *
64
     * @var string
65
     */
66
    protected $collection_name = 'notification';
67
68
    /**
69
     * Message handler.
70
     *
71
     * @var TemplateHandler
72
     */
73
    protected $template;
74
75
    /**
76
     * Constructor.
77
     */
78
    public function __construct(Database $db, Server $server, LoggerInterface $logger, TemplateHandler $template)
79
    {
80
        $this->logger = $logger;
81
        $this->db = $db;
82
        $this->server = $server;
83
        $this->template = $template;
84
    }
85
86
    /**
87
     * Create custom message.
88
     */
89
    public function customMessage(string $subject, string $body): MessageInterface
90
    {
91
        return new UserMessage($subject, $body, $this->template);
92
    }
93
94
    /**
95
     * Node message factory.
96
     */
97
    public function nodeMessage(string $type, NodeInterface $node): MessageInterface
98
    {
99
        return new NodeMessage($type, $this->template, $node);
100
    }
101
102
    /**
103
     * Send notification.
104
     *
105
     * @param User $sender
106
     */
107
    public function notify(Iterable $receiver, ?User $sender, MessageInterface $message, array $context = []): bool
108
    {
109
        if (0 === count($this->adapter)) {
110
            $this->logger->warning('there are no notification adapter enabled, notification can not be sent', [
111
                'category' => get_class($this),
112
            ]);
113
114
            return false;
115
        }
116
117
        foreach ($receiver as $user) {
118
            foreach ($this->adapter as $name => $adapter) {
119
                $this->logger->debug('send notification to user ['.$user->getId().'] via adapter ['.$name.']', [
120
                    'category' => get_class($this),
121
                ]);
122
123
                $adapter->notify($user, $sender, $message, $context);
124
            }
125
        }
126
127
        return true;
128
    }
129
130
    /**
131
     * Has adapter.
132
     */
133
    public function hasAdapter(string $name): bool
134
    {
135
        return isset($this->adapter[$name]);
136
    }
137
138
    /**
139
     * Inject adapter.
140
     *
141
     * @param string $name
142
     *
143
     * @return Notifier
144
     */
145
    public function injectAdapter(AdapterInterface $adapter, ?string $name = null): self
146
    {
147
        if (null === $name) {
148
            $name = get_class($adapter);
149
        }
150
151
        $this->logger->debug('inject notification adapter ['.$name.'] of type ['.get_class($adapter).']', [
152
            'category' => get_class($this),
153
        ]);
154
155
        if ($this->hasAdapter($name)) {
156
            throw new Exception\AdapterNotUnique('adapter '.$name.' is already registered');
157
        }
158
159
        $this->adapter[$name] = $adapter;
160
161
        return $this;
162
    }
163
164
    /**
165
     * Get adapter.
166
     */
167
    public function getAdapter(string $name): AdapterInterface
168
    {
169
        if (!$this->hasAdapter($name)) {
170
            throw new Exception\AdapterNotFound('adapter '.$name.' is not registered');
171
        }
172
173
        return $this->adapter[$name];
174
    }
175
176
    /**
177
     * Get adapters.
178
     */
179
    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...
180
    {
181
        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...
182
            return $this->adapter;
183
        }
184
        $list = [];
185
        foreach ($adapter as $name) {
186
            if (!$this->hasAdapter($name)) {
187
                throw new Exception\AdapterNotFound('adapter '.$name.' is not registered');
188
            }
189
            $list[$name] = $this->adapter[$name];
190
        }
191
192
        return $list;
193
    }
194
195
    /**
196
     * Add notification.
197
     */
198
    public function postNotification(User $receiver, ?User $sender, MessageInterface $message, array $context = []): ObjectId
199
    {
200
        $data = [
201
            'context' => $context,
202
            'subject' => $message->getSubject($receiver),
203
            'body' => $message->getBody($receiver),
204
            'receiver' => $receiver->getId(),
205
        ];
206
207
        if ($sender instanceof User) {
208
            $data['sender'] = $sender->getId();
209
        }
210
211
        $result = $this->db->{$this->collection_name}->insertOne($data);
212
213
        return $result->getInsertedId();
214
    }
215
216
    /**
217
     * Get notifications.
218
     *
219
     * @param int $offset
220
     * @param int $limit
221
     * @param int $total
222
     */
223
    public function getNotifications(User $user, ?int $offset = null, ?int $limit = null, ?int &$total = null): Iterable
224
    {
225
        $total = $this->db->{$this->collection_name}->count(['receiver' => $user->getId()]);
226
        $result = $this->db->{$this->collection_name}->find(['receiver' => $this->server->getIdentity()->getId()], [
227
            'skip' => $offset,
228
            'limit' => $limit,
229
        ]);
230
231
        return $result;
232
    }
233
234
    /**
235
     * Get notification.
236
     */
237
    public function getNotification(ObjectId $id): array
238
    {
239
        $result = $this->db->{$this->collection_name}->findOne([
240
            '_id' => $id,
241
            'receiver' => $this->server->getIdentity()->getId(),
242
        ]);
243
244
        if ($result === null) {
245
            throw new Exception\NotificationNotFound('notification not found');
246
        }
247
248
        return $result;
249
    }
250
251
    /**
252
     * Get notifications.
253
     */
254
    public function deleteNotification(ObjectId $id): bool
255
    {
256
        $result = $this->db->{$this->collection_name}->deleteOne([
257
            '_id' => $id,
258
            'receiver' => $this->server->getIdentity()->getId(),
259
        ]);
260
261
        if (null === $result) {
262
            throw new Exception\NotificationNotFound('notification not found');
263
        }
264
265
        $this->logger->debug('notification ['.$id.'] removed from user ['.$this->server->getIdentity()->getId().']', [
266
            'category' => get_class($this),
267
        ]);
268
269
        return true;
270
    }
271
272
    /**
273
     * Throttle subscriptions.
274
     */
275
    public function throttleSubscriptions(NodeInterface $node, array $user): Notifier
276
    {
277
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
278
        $this->db->subscription->updateMany([
279
            'node' => $node_id,
280
            'user' => [
281
                '$in' => $user,
282
            ],
283
        ], [
284
            '$set' => [
285
                'last_notification' => new UTCDateTime(),
286
            ],
287
        ]);
288
289
        return $this;
290
    }
291
292
    /**
293
     * Get subscription.
294
     */
295
    public function getSubscription(NodeInterface $node, User $user): ?array
296
    {
297
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
298
299
        return $this->db->subscription->findOne([
300
            'node' => $node_id,
301
            'user' => $user->getId(),
302
        ]);
303
    }
304
305
    /**
306
     * Get subscriptions.
307
     */
308
    public function getSubscriptions(NodeInterface $node): Iterable
309
    {
310
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
311
312
        return $this->db->subscription->find([
313
            'node' => $node_id,
314
        ]);
315
    }
316
317
    /**
318
     * Subscribe to node updates.
319
     */
320
    public function subscribeNode(NodeInterface $node, bool $subscribe = true, bool $exclude_me = true, bool $recursive = false): bool
321
    {
322
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
323
        $user_id = $this->server->getIdentity()->getId();
324
325
        if (true === $subscribe) {
326
            $this->logger->debug('user ['.$this->server->getIdentity()->getId().'] subscribes node ['.$node->getId().']', [
327
                'category' => get_class($this),
328
            ]);
329
330
            $subscription = [
331
                'timestamp' => new UTCDateTime(),
332
                'exclude_me' => $exclude_me,
333
                'recursive' => $recursive,
334
                'user' => $user_id,
335
                'node' => $node_id,
336
            ];
337
338
            $this->db->subscription->replaceOne(
339
                [
340
                'user' => $subscription['user'],
341
                'node' => $subscription['node'],
342
            ],
343
            $subscription,
344
                [
345
                'upsert' => true,
346
            ]
347
            );
348
349
            if ($node instanceof Collection && $recursive === true) {
350
                $db = $this->db;
351
                $node->doRecursiveAction(function ($child) use ($db, $subscription) {
352
                    $subscription['node_id'] = $child->getId();
353
                    $db->subscription->replaceOne(
354
                        [
355
                        'user' => $subscription['user'],
356
                        'node' => $subscription['node'],
357
                    ],
358
                    $subscription,
359
                        [
360
                        'upsert' => true,
361
                    ]
362
                    );
363
                });
364
            }
365
        } else {
366
            $this->logger->debug('user ['.$this->server->getIdentity()->getId().'] unsubscribes node ['.$node->getId().']', [
367
                'category' => get_class($this),
368
            ]);
369
370
            $this->db->subscription->deleteOne([
371
                'user' => $user_id,
372
                'node' => $node_id,
373
            ]);
374
375
            if ($node instanceof Collection && $recursive === true) {
376
                $db = $this->db;
377
                $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...
378
                    $db->subscription->deleteOne([
379
                        'user' => $user_id,
380
                        'node' => $node_id,
381
                    ]);
382
                });
383
            }
384
        }
385
386
        return true;
387
    }
388
}
389