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

Subscription::setOptions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 0
cts 16
cp 0
rs 9.6333
c 0
b 0
f 0
cc 4
crap 20
nc 4
nop 1
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\Hook;
13
14
use Balloon\App\Notification\Notifier;
15
use Balloon\Filesystem\Acl;
16
use Balloon\Filesystem\Node\Collection;
17
use Balloon\Filesystem\Node\File;
18
use Balloon\Filesystem\Node\NodeInterface;
19
use Balloon\Hook\AbstractHook;
20
use Balloon\Server;
21
use Balloon\Server\User;
22
use Generator;
23
use InvalidArgumentException;
24
use Psr\Log\LoggerInterface;
25
26
class Subscription extends AbstractHook
27
{
28
    /**
29
     * Notification throttle.
30
     *
31
     * @var int
32
     */
33
    protected $notification_throttle = 120;
34
35
    /**
36
     * Notifier.
37
     *
38
     * @var Notifier
39
     */
40
    protected $notifier;
41
42
    /**
43
     * Server.
44
     *
45
     * @var Server
46
     */
47
    protected $server;
48
49
    /**
50
     * Logger.
51
     *
52
     * @var LoggerInterface
53
     */
54
    protected $logger;
55
56
    /**
57
     * ACL.
58
     *
59
     * @var Acl
60
     */
61
    protected $acl;
62
63
    /**
64
     * Constructor.
65
     */
66
    public function __construct(Notifier $notifier, Server $server, Acl $acl, LoggerInterface $logger, ?Iterable $config = null)
67
    {
68
        $this->notifier = $notifier;
69
        $this->server = $server;
70
        $this->setOptions($config);
71
        $this->logger = $logger;
72
        $this->acl = $acl;
73
    }
74
75
    /**
76
     * Set config.
77
     */
78
    public function setOptions(?Iterable $config = null): self
79
    {
80
        if (null === $config) {
81
            return $this;
82
        }
83
84
        foreach ($config as $option => $value) {
85
            switch ($option) {
86
                case 'notification_throttle':
87
                    $this->{$option} = (int) $value;
88
89
                break;
90
                default:
91
                    throw new InvalidArgumentException('invalid option '.$option.' given');
92
            }
93
        }
94
95
        return $this;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function postCreateCollection(Collection $parent, Collection $node, bool $clone): void
102
    {
103
        $this->notify($node);
104
105
        if ($this->server->getIdentity() === null) {
106
            return;
107
        }
108
109
        $subscription = $this->notifier->getSubscription($parent, $this->server->getIdentity());
110
111
        if ($subscription !== null && $subscription['recursive'] === true) {
112
            $this->notifier->subscribeNode($node, true, $subscription['exclude_me'], $subscription['recursive']);
113
        }
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function postCreateFile(Collection $parent, File $node, bool $clone): void
120
    {
121
        $this->notify($node);
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function postDeleteCollection(Collection $node, bool $force, ?string $recursion, bool $recursion_first): void
128
    {
129
        $this->notify($node);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function postRestoreFile(File $node, int $version): void
136
    {
137
        $this->notify($node);
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function postDeleteFile(File $node, bool $force, ?string $recursion, bool $recursion_first): void
144
    {
145
        $this->notify($node);
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function postPutFile(File $node): void
152
    {
153
        $this->notify($node);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function postSaveNodeAttributes(NodeInterface $node, array $attributes, array $remove, ?string $recursion, bool $recursion_first): void
160
    {
161
        $raw = $node->getRawAttributes();
162
        if (in_array('parent', $attributes, true) && $raw['parent'] !== $node->getAttributes()['parent']) {
163
            $this->notify($node);
164
        }
165
    }
166
167
    /**
168
     * Check if we need to notify.
169
     */
170
    protected function notify(NodeInterface $node): bool
171
    {
172
        $receiver = $this->getReceiver($node);
173
        $parent = $node->getParent();
174
175
        if ($parent !== null) {
176
            $parents = $this->getReceiver($parent);
177
            $parents = array_diff($parents, $receiver);
178
179
            $this->send($node, $parent, $parents);
180
        }
181
182
        $this->send($node, $node, $receiver);
183
184
        return true;
185
    }
186
187
    /**
188
     * Send.
189
     */
190
    protected function send(NodeInterface $node, NodeInterface $subscription, array $receiver)
191
    {
192
        if (empty($receiver)) {
193
            $this->logger->debug('skip subscription notification for node ['.$node->getId().'] due empty receiver list', [
194
                'category' => get_class($this),
195
            ]);
196
197
            return false;
198
        }
199
200
        $receiver = $this->server->getUsers(['_id' => ['$in' => $receiver]]);
201
        $receiver = $this->filterAccess($node, $receiver);
202
203
        $message = $this->notifier->compose('subscription', [
204
            'subscription' => $subscription,
205
            'node' => $node,
206
        ]);
207
208
        return $this->notifier->notify($receiver, $this->server->getIdentity(), $message);
209
    }
210
211
    /**
212
     * Get receiver list.
213
     */
214
    protected function getReceiver(NodeInterface $node): array
215
    {
216
        $subs = $this->notifier->getSubscriptions($node);
217
218
        $receiver = [];
219
        $user_id = null;
220
        if ($this->server->getIdentity() instanceof User) {
221
            $user_id = $this->server->getIdentity()->getId();
222
        }
223
224
        foreach ($subs as $key => $subscription) {
225
            if (isset($subscription['last_notification']) && ($subscription['last_notification']->toDateTime()->format('U') + $this->notification_throttle) > time()) {
226
                $this->logger->debug('skip message for user ['.$subscription['user'].'], message within throttle time range of ['.$this->notification_throttle.'s]', [
227
                    'category' => get_class($this),
228
                ]);
229
            } elseif ($subscription['user'] == $user_id && $subscription['exclude_me'] === true) {
230
                $this->logger->debug('skip message for user ['.$user_id.'], user excludes own actions in node ['.$node->getId().']', [
231
                    'category' => get_class($this),
232
                ]);
233
            } else {
234
                $receiver[] = $subscription['user'];
235
            }
236
        }
237
238
        $this->notifier->throttleSubscriptions($node, $receiver);
239
240
        return $receiver;
241
    }
242
243
    /**
244
     * Only send notifcation if node is accessible by user.
245
     */
246
    protected function filterAccess(NodeInterface $node, iterable $receiver): Generator
247
    {
248
        $users = [];
249
250
        foreach ($receiver as $user) {
251
            if ($this->acl->isAllowed($node, 'r', $user)) {
252
                $users[] = $user->getId();
253
                yield $user;
254
            } else {
255
                $this->logger->debug('skip message for user ['.$user->getId().'], node ['.$node->getId().'] not accessible by this user', [
256
                    'category' => get_class($this),
257
                ]);
258
            }
259
        }
260
261
        $this->notifier->throttleSubscriptions($node, $users);
262
    }
263
}
264