Completed
Pull Request — master (#149)
by Raffael
05:07
created

Subscription::filterAccess()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 0
cts 16
cp 0
rs 9.6666
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 12
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\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
     * @param Notification $notifier
67
     */
68
    public function __construct(Notifier $notifier, Server $server, Acl $acl, LoggerInterface $logger, ?Iterable $config = null)
69
    {
70
        $this->notifier = $notifier;
71
        $this->server = $server;
72
        $this->setOptions($config);
73
        $this->logger = $logger;
74
        $this->acl = $acl;
75
    }
76
77
    /**
78
     * Set config.
79
     *
80
     * @param iterable $config
81
     *
82
     * @return Subscription
83
     */
84
    public function setOptions(?Iterable $config = null): self
85
    {
86
        if (null === $config) {
87
            return $this;
88
        }
89
90
        foreach ($config as $option => $value) {
91
            switch ($option) {
92
                case 'notification_throttle':
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...
93
                    $this->{$option} = (int) $value;
94
95
                break;
96
                default:
97
                    throw new InvalidArgumentException('invalid option '.$option.' given');
98
            }
99
        }
100
101
        return $this;
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function postCreateCollection(Collection $parent, Collection $node, bool $clone): void
108
    {
109
        $this->notify($node);
110
        $subscription = $this->notifier->getSubscription($parent, $this->server->getIdentity());
111
112
        if ($subscription !== null && $subscription['recursive'] === true) {
113
            $this->notifier->subscribeNode($node, true, $subscription['exclude_me'], $subscription['recursive']);
114
        }
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function postCreateFile(Collection $parent, File $node, bool $clone): void
121
    {
122
        $this->notify($node);
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function postDeleteCollection(Collection $node, bool $force, ?string $recursion, bool $recursion_first): void
129
    {
130
        $this->notify($node);
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function postRestoreFile(File $node, int $version): void
137
    {
138
        $this->notify($node);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function postDeleteFile(File $node, bool $force, ?string $recursion, bool $recursion_first): void
145
    {
146
        $this->notify($node);
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function postPutFile(File $node): void
153
    {
154
        $this->notify($node);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function postSaveNodeAttributes(NodeInterface $node, array $attributes, array $remove, ?string $recursion, bool $recursion_first): void
161
    {
162
        $raw = $node->getRawAttributes();
163
        if (in_array('parent', $attributes, true) && $raw['parent'] !== $node->getAttributes()['parent']) {
164
            $this->notify($node);
165
        }
166
    }
167
168
    /**
169
     * Check if we need to notify.
170
     */
171
    protected function notify(NodeInterface $node): bool
172
    {
173
        $receiver = $this->getReceiver($node);
174
        $parent = $node->getParent();
175
        if ($parent !== null) {
176
            $parents = $this->getReceiver($parent);
177
            $receiver = array_merge($parents, $receiver);
178
        }
179
180
        if (empty($receiver)) {
181
            $this->logger->debug('skip subscription notification for node ['.$node->getId().'] due empty receiver list', [
182
                'category' => get_class($this),
183
            ]);
184
185
            return false;
186
        }
187
188
        $receiver = $this->server->getUsers(['_id' => ['$in' => $receiver]]);
189
        $receiver = $this->filterAccess($node, $receiver);
190
        $message = $this->notifier->nodeMessage('subscription', $node);
191
192
        return $this->notifier->notify($receiver, $this->server->getIdentity(), $message);
193
    }
194
195
    /**
196
     * Get receiver list.
197
     */
198
    protected function getReceiver(NodeInterface $node): array
199
    {
200
        $subs = $this->notifier->getSubscriptions($node);
201
202
        $receiver = [];
203
        $user_id = null;
204
        if ($this->server->getIdentity() instanceof User) {
205
            $user_id = $this->server->getIdentity()->getId();
206
        }
207
208
        foreach ($subs as $key => $subscription) {
209
            if (isset($subscription['last_notification']) && ($subscription['last_notification']->toDateTime()->format('U') + $this->notification_throttle) > time()) {
210
                $this->logger->debug('skip message for user ['.$subscription['user'].'], message within throttle time range of ['.$this->notification_throttle.'s]', [
211
                    'category' => get_class($this),
212
                ]);
213
            } elseif ($subscription['user'] == $user_id && $subscription['exclude_me'] === true) {
214
                $this->logger->debug('skip message for user ['.$user_id.'], user excludes own actions in node ['.$node->getId().']', [
215
                    'category' => get_class($this),
216
                ]);
217
            } else {
218
                $receiver[] = $subscription['user'];
219
            }
220
        }
221
222
        return $receiver;
223
    }
224
225
    /**
226
     * Only send notifcation if node is accessible by user.
227
     */
228
    protected function filterAccess(NodeInterface $node, iterable $receiver): Generator
229
    {
230
        $users = [];
231
232
        foreach ($receiver as $user) {
233
            if ($this->acl->isAllowed($node, 'r', $user)) {
234
                $users[] = $user->getId();
235
                yield $user;
236
            } else {
237
                $this->logger->debug('skip message for user ['.$user->getId().'], node ['.$node->getId().'] not accessible by this user', [
238
                    'category' => get_class($this),
239
                ]);
240
            }
241
        }
242
243
        $this->notifier->throttleSubscriptions($node, $users);
244
        $this->notifier->throttleSubscriptions($node->getParent(), $users);
245
    }
246
}
247