Test Failed
Push — master ( 8c814b...380005 )
by Raffael
08:49
created

Notifier::throttleSubscriptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 15
cp 0
rs 9.7333
c 0
b 0
f 0
cc 2
crap 6
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 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
     * Node message factory.
88
     */
89
    public function compose(string $type, array $context = []): MessageInterface
90
    {
91
        return new Message($type, $this->template, $context);
92
    }
93
94
    /**
95
     * Send notification.
96
     */
97
    public function notify(iterable $receiver, ?User $sender, MessageInterface $message): bool
98
    {
99
        if (0 === count($this->adapter)) {
100
            $this->logger->warning('there are no notification adapter enabled, notification can not be sent', [
101
                'category' => get_class($this),
102
            ]);
103
104
            return false;
105
        }
106
107
        foreach ($receiver as $user) {
108
            foreach ($this->adapter as $name => $adapter) {
109
                $this->logger->debug('send notification to user ['.$user->getId().'] via adapter ['.$name.']', [
110
                    'category' => get_class($this),
111
                ]);
112
113
                $adapter->notify($user, $sender, $message);
114
            }
115
        }
116
117
        return true;
118
    }
119
120
    /**
121
     * Has adapter.
122
     */
123
    public function hasAdapter(string $name): bool
124
    {
125
        return isset($this->adapter[$name]);
126
    }
127
128
    /**
129
     * Inject adapter.
130
     */
131
    public function injectAdapter(AdapterInterface $adapter, ?string $name = null): self
132
    {
133
        if (null === $name) {
134
            $name = get_class($adapter);
135
        }
136
137
        $this->logger->debug('inject notification adapter ['.$name.'] of type ['.get_class($adapter).']', [
138
            'category' => get_class($this),
139
        ]);
140
141
        if ($this->hasAdapter($name)) {
142
            throw new Exception\AdapterNotUnique('adapter '.$name.' is already registered');
143
        }
144
145
        $this->adapter[$name] = $adapter;
146
147
        return $this;
148
    }
149
150
    /**
151
     * Get adapter.
152
     */
153
    public function getAdapter(string $name): AdapterInterface
154
    {
155
        if (!$this->hasAdapter($name)) {
156
            throw new Exception\AdapterNotFound('adapter '.$name.' is not registered');
157
        }
158
159
        return $this->adapter[$name];
160
    }
161
162
    /**
163
     * Get adapters.
164
     */
165
    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...
166
    {
167
        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...
168
            return $this->adapter;
169
        }
170
        $list = [];
171
        foreach ($adapter as $name) {
172
            if (!$this->hasAdapter($name)) {
173
                throw new Exception\AdapterNotFound('adapter '.$name.' is not registered');
174
            }
175
            $list[$name] = $this->adapter[$name];
176
        }
177
178
        return $list;
179
    }
180
181
    /**
182
     * Add notification.
183
     */
184
    public function postNotification(User $receiver, ?User $sender, MessageInterface $message): ObjectId
185
    {
186
        $data = [
187
            'subject' => $message->getSubject($receiver),
188
            'body' => $message->getBody($receiver),
189
            'receiver' => $receiver->getId(),
190
            'locale' => $receiver->getAttributes()['locale'],
191
            'type' => $message->getType(),
192
        ];
193
194
        if ($sender instanceof User) {
195
            $data['sender'] = $sender->getId();
196
        }
197
198
        if (isset($message->getContext()['node'])) {
199
            $data['node'] = $message->getContext()['node']->getId();
200
        }
201
202
        $result = $this->db->{$this->collection_name}->insertOne($data);
203
204
        return $result->getInsertedId();
205
    }
206
207
    /**
208
     * Get notifications.
209
     */
210
    public function getNotifications(User $user, array $query = [], ?int $offset = null, ?int $limit = null, ?int &$total = null): iterable
211
    {
212
        $filter = ['receiver' => $user->getId()];
213
        if (!empty($query)) {
214
            $filter = [
215
                '$and' => [
216
                    $filter,
217
                    $query,
218
                ],
219
            ];
220
        }
221
222
        $total = $this->db->{$this->collection_name}->count($filter);
223
        $result = $this->db->{$this->collection_name}->find($filter, [
224
            'skip' => $offset,
225
            'limit' => $limit,
226
        ]);
227
228
        return $result;
229
    }
230
231
    /**
232
     * Get notification.
233
     */
234
    public function getNotification(ObjectId $id): array
235
    {
236
        $result = $this->db->{$this->collection_name}->findOne([
237
            '_id' => $id,
238
            'receiver' => $this->server->getIdentity()->getId(),
239
        ]);
240
241
        if ($result === null) {
242
            throw new Exception\NotificationNotFound('notification not found');
243
        }
244
245
        return $result;
246
    }
247
248
    /**
249
     * Get notifications.
250
     */
251
    public function deleteNotification(ObjectId $id): bool
252
    {
253
        $result = $this->db->{$this->collection_name}->deleteOne([
254
            '_id' => $id,
255
            'receiver' => $this->server->getIdentity()->getId(),
256
        ]);
257
258
        if (null === $result) {
259
            throw new Exception\NotificationNotFound('notification not found');
260
        }
261
262
        $this->logger->debug('notification ['.$id.'] removed from user ['.$this->server->getIdentity()->getId().']', [
263
            'category' => get_class($this),
264
        ]);
265
266
        return true;
267
    }
268
269
    /**
270
     * Throttle subscriptions.
271
     */
272
    public function throttleSubscriptions(NodeInterface $node, array $user): Notifier
273
    {
274
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
275
        $this->db->subscription->updateMany([
276
            'node' => $node_id,
277
            'user' => [
278
                '$in' => $user,
279
            ],
280
        ], [
281
            '$set' => [
282
                'last_notification' => new UTCDateTime(),
283
            ],
284
        ]);
285
286
        return $this;
287
    }
288
289
    /**
290
     * Get subscription.
291
     */
292
    public function getSubscription(NodeInterface $node, User $user): ?array
293
    {
294
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
295
296
        return $this->db->subscription->findOne([
297
            'node' => $node_id,
298
            'user' => $user->getId(),
299
        ]);
300
    }
301
302
    /**
303
     * Get subscriptions.
304
     */
305
    public function getSubscriptions(NodeInterface $node): iterable
306
    {
307
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
308
309
        return $this->db->subscription->find([
310
            'node' => $node_id,
311
        ]);
312
    }
313
314
    /**
315
     * Subscribe to node updates.
316
     */
317
    public function subscribeNode(NodeInterface $node, bool $subscribe = true, bool $exclude_me = true, bool $recursive = false): bool
318
    {
319
        $node_id = $node->isReference() ? $node->getShareId() : $node->getId();
320
        $user_id = $this->server->getIdentity()->getId();
321
322
        if (true === $subscribe) {
323
            $this->logger->debug('user ['.$this->server->getIdentity()->getId().'] subscribes node ['.$node->getId().']', [
324
                'category' => get_class($this),
325
            ]);
326
327
            $subscription = [
328
                'timestamp' => new UTCDateTime(),
329
                'exclude_me' => $exclude_me,
330
                'recursive' => $recursive,
331
                'user' => $user_id,
332
                'node' => $node_id,
333
            ];
334
335
            $this->db->subscription->replaceOne(
336
                [
337
                'user' => $subscription['user'],
338
                'node' => $subscription['node'],
339
            ],
340
                $subscription,
341
                [
342
                'upsert' => true,
343
            ]
344
            );
345
346
            if ($node instanceof Collection && $recursive === true) {
347
                $db = $this->db;
348
                $node->doRecursiveAction(function ($child) use ($db, $subscription) {
349
                    $subscription['node'] = $child->getId();
350
                    $db->subscription->replaceOne(
351
                        [
352
                        'user' => $subscription['user'],
353
                        'node' => $subscription['node'],
354
                    ],
355
                        $subscription,
356
                        [
357
                        'upsert' => true,
358
                    ]
359
                    );
360
                });
361
            }
362
        } else {
363
            $this->logger->debug('user ['.$this->server->getIdentity()->getId().'] unsubscribes node ['.$node->getId().']', [
364
                'category' => get_class($this),
365
            ]);
366
367
            $this->db->subscription->deleteOne([
368
                'user' => $user_id,
369
                'node' => $node_id,
370
            ]);
371
372
            if ($node instanceof Collection && $recursive === true) {
373
                $db = $this->db;
374
                $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...
375
                    $db->subscription->deleteOne([
376
                        'user' => $user_id,
377
                        'node' => $node_id,
378
                    ]);
379
                });
380
            }
381
        }
382
383
        return true;
384
    }
385
}
386