Completed
Pull Request — master (#444)
by Raffael
05:17
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 Method

Rating   Name   Duplication   Size   Complexity  
A Subscription::postDeleteCollection() 0 4 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\Exception;
15
use Balloon\App\Notification\Notifier;
16
use Balloon\Filesystem\Acl;
17
use Balloon\Filesystem\Node\Collection;
18
use Balloon\Filesystem\Node\File;
19
use Balloon\Filesystem\Node\NodeInterface;
20
use Balloon\Hook\AbstractHook;
21
use Balloon\Server;
22
use Balloon\Server\User;
23
use Generator;
24
use Psr\Log\LoggerInterface;
25
26
class Subscription extends AbstractHook
27
{
28
    /**
29
     * Notifier.
30
     *
31
     * @var Notifier
32
     */
33
    protected $notifier;
34
35
    /**
36
     * Server.
37
     *
38
     * @var Server
39
     */
40
    protected $server;
41
42
    /**
43
     * Logger.
44
     *
45
     * @var LoggerInterface
46
     */
47
    protected $logger;
48
49
    /**
50
     * ACL.
51
     *
52
     * @var Acl
53
     */
54
    protected $acl;
55
56
    /**
57
     * Constructor.
58
     */
59
    public function __construct(Notifier $notifier, Server $server, Acl $acl, LoggerInterface $logger)
60
    {
61
        $this->notifier = $notifier;
62
        $this->server = $server;
63
        $this->logger = $logger;
64
        $this->acl = $acl;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function postCreateCollection(Collection $parent, Collection $node, bool $clone): void
71
    {
72
        $this->notify($node);
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function postCreateFile(Collection $parent, File $node, bool $clone): void
79
    {
80
        $this->notify($node);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function postDeleteCollection(Collection $node, bool $force, ?string $recursion, bool $recursion_first): void
87
    {
88
        $this->notify($node);
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function postRestoreFile(File $node, int $version): void
95
    {
96
        $this->notify($node);
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function postDeleteFile(File $node, bool $force, ?string $recursion, bool $recursion_first): void
103
    {
104
        $this->notify($node);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function postPutFile(File $node): void
111
    {
112
        $this->notify($node);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function postSaveNodeAttributes(NodeInterface $node, array $attributes, array $remove, ?string $recursion, bool $recursion_first): void
119
    {
120
        $raw = $node->getRawAttributes();
121
        if (in_array('parent', $attributes, true) && $raw['parent'] !== $node->getAttributes()['parent']) {
122
            $this->notify($node);
123
        }
124
    }
125
126
    /**
127
     * Check if we need to notify.
128
     */
129
    protected function notify(NodeInterface $node): bool
130
    {
131
        if ($node instanceof File && $node->isTemporaryFile()) {
132
            $this->logger->debug('skip subscription notification for node temporary file ['.$node->getId().'] (matches temporary file pattern)', [
133
                'category' => get_class($this),
134
            ]);
135
136
            return false;
137
        }
138
139
        $receiver = $this->getReceiver($node);
140
        $this->send($node, $node, $receiver);
141
142
        return true;
143
    }
144
145
    /**
146
     * Send.
147
     */
148
    protected function send(NodeInterface $node, NodeInterface $subscription, array $receiver)
149
    {
150
        if (empty($receiver)) {
151
            $this->logger->debug('skip subscription notification for node ['.$node->getId().'] due empty receiver list', [
152
                'category' => get_class($this),
153
            ]);
154
155
            return false;
156
        }
157
158
        $receiver = $this->server->getUsers(['_id' => ['$in' => $receiver]]);
159
        $receiver = $this->filterAccess($node, $receiver);
160
161
        $message = $this->notifier->compose('subscription', [
162
            'subscription' => $subscription,
163
            'node' => $node,
164
        ]);
165
166
        try {
167
            return $this->notifier->notify($receiver, $this->server->getIdentity(), $message);
168
        } catch (Exception\NoAdapterAvailable $e) {
169
            $this->logger->error('subscription notification could not be sent', [
170
                'category' => get_class($this),
171
                'exception' => $e,
172
            ]);
173
        }
174
    }
175
176
    /**
177
     * Get receiver list.
178
     */
179
    protected function getReceiver(NodeInterface $node): array
180
    {
181
        $subs = $this->notifier->getAllSubscriptions($node);
182
183
        $receiver = [];
184
        $user_id = null;
185
        if ($this->server->getIdentity() instanceof User) {
186
            $user_id = $this->server->getIdentity()->getId();
187
        }
188
189
        $subscriptions = [];
190
191
        foreach ($subs as $key => $subscription) {
192
            $throttle = $subscription['throttle'] ?? $this->notifier->getThrottleTime();
193
194
            if (isset($subscription['last_notification']) && ($subscription['last_notification']->toDateTime()->format('U') + $throttle) > time()) {
195
                $this->logger->debug('skip message for user ['.$subscription['user'].'], message within throttle time range of ['.$throttle.'s]', [
196
                    'category' => get_class($this),
197
                ]);
198
            } elseif ($subscription['user'] == $user_id && $subscription['exclude_me'] === true) {
199
                $this->logger->debug('skip message for user ['.$user_id.'], user excludes own actions in node ['.$node->getId().']', [
200
                    'category' => get_class($this),
201
                ]);
202
            } else {
203
                $receiver[] = $subscription['user'];
204
                $subscriptions[] = $subscription['_id'];
205
            }
206
        }
207
208
        $this->notifier->throttleSubscriptions($subscriptions);
209
210
        return $receiver;
211
    }
212
213
    /**
214
     * Only send notifcation if node is accessible by user.
215
     */
216
    protected function filterAccess(NodeInterface $node, iterable $receiver): Generator
217
    {
218
        $users = [];
219
220
        foreach ($receiver as $user) {
221
            if ($this->acl->isAllowed($node, 'r', $user)) {
222
                $users[] = $user->getId();
223
                yield $user;
224
            } else {
225
                $this->logger->debug('skip message for user ['.$user->getId().'], node ['.$node->getId().'] not accessible by this user', [
226
                    'category' => get_class($this),
227
                ]);
228
            }
229
        }
230
231
        //$this->notifier->throttleSubscriptions($node, $users);
232
    }
233
}
234