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; |
13
|
|
|
|
14
|
|
|
use Balloon\Filesystem\Acl; |
15
|
|
|
use Balloon\Filesystem\Storage; |
16
|
|
|
use Balloon\Server\Group; |
17
|
|
|
use Balloon\Server\User; |
18
|
|
|
use Generator; |
19
|
|
|
use InvalidArgumentException; |
20
|
|
|
use Micro\Auth\Identity; |
21
|
|
|
use MongoDB\BSON\Binary; |
22
|
|
|
use MongoDB\BSON\ObjectId; |
23
|
|
|
use MongoDB\BSON\UTCDateTime; |
24
|
|
|
use MongoDB\Database; |
25
|
|
|
use Psr\Log\LoggerInterface; |
26
|
|
|
|
27
|
|
|
class Server |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* Database. |
31
|
|
|
* |
32
|
|
|
* @var Database |
33
|
|
|
*/ |
34
|
|
|
protected $db; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Storage. |
38
|
|
|
* |
39
|
|
|
* @var Storage |
40
|
|
|
*/ |
41
|
|
|
protected $storage; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* LoggerInterface. |
45
|
|
|
* |
46
|
|
|
* @var LoggerInterface |
47
|
|
|
*/ |
48
|
|
|
protected $logger; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Hook. |
52
|
|
|
* |
53
|
|
|
* @var Hook |
54
|
|
|
*/ |
55
|
|
|
protected $hook; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Authenticated identity. |
59
|
|
|
* |
60
|
|
|
* @var User |
61
|
|
|
*/ |
62
|
|
|
protected $identity; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Acl. |
66
|
|
|
* |
67
|
|
|
* @var Acl |
68
|
|
|
*/ |
69
|
|
|
protected $acl; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Temporary store. |
73
|
|
|
* |
74
|
|
|
* @var string |
75
|
|
|
*/ |
76
|
|
|
protected $temp_dir = '/tmp/balloon'; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Max file version. |
80
|
|
|
* |
81
|
|
|
* @var int |
82
|
|
|
*/ |
83
|
|
|
protected $max_file_version = 8; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Max file size. |
87
|
|
|
* |
88
|
|
|
* @var int |
89
|
|
|
*/ |
90
|
|
|
protected $max_file_size = 17179869184; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Password policy. |
94
|
|
|
* |
95
|
|
|
* @var string |
96
|
|
|
*/ |
97
|
|
|
protected $password_policy = '/.*/'; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Password hash. |
101
|
|
|
* |
102
|
|
|
* @var int |
103
|
|
|
*/ |
104
|
|
|
protected $password_hash = PASSWORD_DEFAULT; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Server url. |
108
|
|
|
* |
109
|
|
|
* @var string |
110
|
|
|
*/ |
111
|
|
|
protected $server_url = 'https://localhost'; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Initialize. |
115
|
|
|
* |
116
|
|
|
* @param Database $db |
117
|
|
|
* @param Storage $storage |
118
|
|
|
* @param LoggerInterface $logger |
119
|
|
|
* @param Hook $hook |
120
|
|
|
* @param Acl $acl |
121
|
|
|
* @param iterable $config |
122
|
|
|
*/ |
123
|
|
|
public function __construct(Database $db, Storage $storage, LoggerInterface $logger, Hook $hook, Acl $acl, ?Iterable $config = null) |
124
|
|
|
{ |
125
|
|
|
$this->db = $db; |
126
|
|
|
$this->storage = $storage; |
127
|
|
|
$this->logger = $logger; |
128
|
|
|
$this->hook = $hook; |
129
|
|
|
$this->acl = $acl; |
130
|
|
|
|
131
|
|
|
$this->setOptions($config); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Set options. |
136
|
|
|
* |
137
|
|
|
* @param iterable $config |
138
|
|
|
* |
139
|
|
|
* @return Server |
140
|
|
|
*/ |
141
|
|
|
public function setOptions(?Iterable $config = null): self |
142
|
|
|
{ |
143
|
|
|
if (null === $config) { |
144
|
|
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
foreach ($config as $name => $value) { |
148
|
|
|
switch ($name) { |
149
|
|
|
case 'temp_dir': |
|
|
|
|
150
|
|
|
case 'password_policy': |
151
|
|
|
case 'server_url': |
152
|
|
|
$this->{$name} = (string) $value; |
153
|
|
|
|
154
|
|
|
break; |
155
|
|
|
case 'max_file_version': |
156
|
|
|
case 'max_file_size': |
157
|
|
|
case 'password_hash': |
158
|
|
|
$this->{$name} = (int) $value; |
159
|
|
|
|
160
|
|
|
break; |
161
|
|
|
default: |
162
|
|
|
throw new InvalidArgumentException('invalid option '.$name.' given'); |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return $this; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Get server url. |
171
|
|
|
* |
172
|
|
|
* @return string |
173
|
|
|
*/ |
174
|
|
|
public function getServerUrl(): string |
175
|
|
|
{ |
176
|
|
|
return $this->server_url; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Get temporary directory. |
181
|
|
|
* |
182
|
|
|
* @return string |
183
|
|
|
*/ |
184
|
|
|
public function getTempDir(): string |
185
|
|
|
{ |
186
|
|
|
return $this->temp_dir; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Get max file version. |
191
|
|
|
* |
192
|
|
|
* @return int |
193
|
|
|
*/ |
194
|
|
|
public function getMaxFileVersion(): int |
195
|
|
|
{ |
196
|
|
|
return $this->max_file_version; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Get max file size. |
201
|
|
|
* |
202
|
|
|
* @return int |
203
|
|
|
*/ |
204
|
|
|
public function getMaxFileSize(): int |
205
|
|
|
{ |
206
|
|
|
return $this->max_file_size; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Filesystem factory. |
211
|
|
|
* |
212
|
|
|
* @return Filesystem |
213
|
|
|
*/ |
214
|
|
|
public function getFilesystem(?User $user = null): Filesystem |
215
|
|
|
{ |
216
|
|
|
if (null !== $user) { |
217
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $user); |
218
|
|
|
} |
219
|
|
|
if ($this->identity instanceof User) { |
220
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $this->identity); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Verify group attributes. |
228
|
|
|
* |
229
|
|
|
* @param array $attributes |
230
|
|
|
* |
231
|
|
|
* @return array |
232
|
|
|
*/ |
233
|
|
|
public function validateGroupAttributes(array $attributes): array |
234
|
|
|
{ |
235
|
|
|
foreach ($attributes as $attribute => &$value) { |
236
|
|
|
switch ($attribute) { |
237
|
|
|
case 'namespace': |
238
|
|
|
if (!is_string($value)) { |
239
|
|
|
throw new Group\Exception\InvalidArgument( |
240
|
|
|
$attribute.' must be a valid string', |
241
|
|
|
Group\Exception\InvalidArgument::INVALID_NAMESPACE |
242
|
|
|
); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
break; |
246
|
|
|
case 'name': |
247
|
|
|
if (!is_string($value)) { |
248
|
|
|
throw new Group\Exception\InvalidArgument( |
249
|
|
|
$attribute.' must be a valid string', |
250
|
|
|
Group\Exception\InvalidArgument::INVALID_NAME |
251
|
|
|
); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
break; |
255
|
|
|
case 'optional': |
256
|
|
|
if (!is_array($value)) { |
257
|
|
|
throw new Group\Exception\InvalidArgument( |
258
|
|
|
'optional group attributes must be an array', |
259
|
|
|
Group\Exception\InvalidArgument::INVALID_OPTIONAL |
260
|
|
|
); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
break; |
264
|
|
|
case 'member': |
265
|
|
|
if (!is_array($value)) { |
266
|
|
|
throw new Group\Exception\InvalidArgument( |
267
|
|
|
'member must be an array of user', |
268
|
|
|
Group\Exception\InvalidArgument::INVALID_MEMBER |
269
|
|
|
); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$valid = []; |
273
|
|
|
foreach ($value as $id) { |
274
|
|
|
if ($id instanceof User) { |
275
|
|
|
$id = $id->getId(); |
276
|
|
|
} else { |
277
|
|
|
$id = new ObjectId($id); |
278
|
|
|
if (!$this->userExists($id)) { |
279
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if (!in_array($id, $valid)) { |
284
|
|
|
$valid[] = $id; |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
$value = $valid; |
289
|
|
|
|
290
|
|
|
break; |
291
|
|
|
default: |
292
|
|
|
throw new Group\Exception\InvalidArgument( |
293
|
|
|
'invalid attribute '.$attribute.' given', |
294
|
|
|
Group\Exception\InvalidArgument::INVALID_ATTRIBUTE |
295
|
|
|
); |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
return $attributes; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Verify user attributes. |
304
|
|
|
* |
305
|
|
|
* @param array $attributes |
306
|
|
|
* |
307
|
|
|
* @return array |
308
|
|
|
*/ |
309
|
|
|
public function validateUserAttributes(array $attributes): array |
310
|
|
|
{ |
311
|
|
|
foreach ($attributes as $attribute => &$value) { |
312
|
|
|
switch ($attribute) { |
313
|
|
|
case 'username': |
314
|
|
|
if (!preg_match('/^[A-Za-z0-9\.-_\@]+$/', $value)) { |
315
|
|
|
throw new User\Exception\InvalidArgument( |
316
|
|
|
'username does not match required regex /^[A-Za-z0-9\.-_\@]+$/', |
317
|
|
|
User\Exception\InvalidArgument::INVALID_USERNAME |
318
|
|
|
); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
if ($this->usernameExists($value)) { |
322
|
|
|
throw new User\Exception\NotUnique('user does already exists'); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
break; |
326
|
|
|
case 'password': |
327
|
|
|
if (!preg_match($this->password_policy, $value)) { |
328
|
|
|
throw new User\Exception\InvalidArgument( |
329
|
|
|
'password does not follow password policy '.$this->password_policy, |
330
|
|
|
User\Exception\InvalidArgument::INVALID_PASSWORD |
331
|
|
|
); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$value = password_hash($value, $this->password_hash); |
335
|
|
|
|
336
|
|
|
break; |
337
|
|
|
case 'soft_quota': |
338
|
|
|
case 'hard_quota': |
339
|
|
|
if (!is_numeric($value)) { |
340
|
|
|
throw new User\Exception\InvalidArgument( |
341
|
|
|
$attribute.' must be numeric', |
342
|
|
|
User\Exception\InvalidArgument::INVALID_QUOTA |
343
|
|
|
); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
break; |
347
|
|
|
case 'avatar': |
348
|
|
|
if (!$value instanceof Binary) { |
|
|
|
|
349
|
|
|
throw new User\Exception\InvalidArgument( |
350
|
|
|
'avatar must be an instance of Binary', |
351
|
|
|
User\Exception\InvalidArgument::INVALID_AVATAR |
352
|
|
|
); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
break; |
356
|
|
|
case 'mail': |
357
|
|
|
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { |
358
|
|
|
throw new User\Exception\InvalidArgument( |
359
|
|
|
'mail address given is invalid', |
360
|
|
|
User\Exception\InvalidArgument::INVALID_MAIL |
361
|
|
|
); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
break; |
365
|
|
|
case 'admin': |
366
|
|
|
$value = (bool) $value; |
367
|
|
|
|
368
|
|
|
break; |
369
|
|
|
case 'locale': |
370
|
|
|
if (!preg_match('#^[a-z]{2}_[A-Z]{2}$#', $value)) { |
371
|
|
|
throw new User\Exception\InvalidArgument( |
372
|
|
|
'invalid locale given, must be according to format a-z_A-Z', |
373
|
|
|
User\Exception\InvalidArgument::INVALID_LOCALE |
374
|
|
|
); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
break; |
378
|
|
|
case 'namespace': |
379
|
|
|
if (!is_string($value)) { |
380
|
|
|
throw new User\Exception\InvalidArgument( |
381
|
|
|
'namespace must be a valid string', |
382
|
|
|
User\Exception\InvalidArgument::INVALID_NAMESPACE |
383
|
|
|
); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
break; |
387
|
|
|
case 'optional': |
388
|
|
|
if (!is_array($value)) { |
389
|
|
|
throw new User\Exception\InvalidArgument( |
390
|
|
|
'optional user attributes must be an array', |
391
|
|
|
User\Exception\InvalidArgument::INVALID_OPTIONAL |
392
|
|
|
); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
break; |
396
|
|
|
default: |
397
|
|
|
throw new User\Exception\InvalidArgument( |
398
|
|
|
'invalid attribute '.$attribute.' given', |
399
|
|
|
User\Exception\InvalidArgument::INVALID_ATTRIBUTE |
400
|
|
|
); |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
return $attributes; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* Add user. |
409
|
|
|
* |
410
|
|
|
* @param string $username |
411
|
|
|
* @param array $attributes |
412
|
|
|
* |
413
|
|
|
* @return ObjectId |
414
|
|
|
*/ |
415
|
|
|
public function addUser(string $username, array $attributes = []): ObjectId |
416
|
|
|
{ |
417
|
|
|
$attributes['username'] = $username; |
418
|
|
|
$attributes = $this->validateUserAttributes($attributes); |
419
|
|
|
|
420
|
|
|
$defaults = [ |
421
|
|
|
'created' => new UTCDateTime(), |
422
|
|
|
'changed' => new UTCDateTime(), |
423
|
|
|
'deleted' => false, |
424
|
|
|
]; |
425
|
|
|
|
426
|
|
|
$attributes = array_merge($defaults, $attributes); |
427
|
|
|
$result = $this->db->user->insertOne($attributes); |
428
|
|
|
|
429
|
|
|
return $result->getInsertedId(); |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Check if user exists. |
434
|
|
|
* |
435
|
|
|
* @return bool |
436
|
|
|
*/ |
437
|
|
|
public function usernameExists(string $username): bool |
438
|
|
|
{ |
439
|
|
|
return 1 === $this->db->user->count(['username' => $username]); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Check if user exists. |
444
|
|
|
* |
445
|
|
|
* @return bool |
446
|
|
|
*/ |
447
|
|
|
public function userExists(ObjectId $id): bool |
448
|
|
|
{ |
449
|
|
|
return 1 === $this->db->user->count(['_id' => $id]); |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Check if user exists. |
454
|
|
|
* |
455
|
|
|
* @return bool |
456
|
|
|
*/ |
457
|
|
|
public function groupExists(ObjectId $id): bool |
458
|
|
|
{ |
459
|
|
|
return 1 === $this->db->group->count(['_id' => $id]); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* Get user by id. |
464
|
|
|
* |
465
|
|
|
* @param ObjectId $id |
466
|
|
|
* |
467
|
|
|
* @return User |
468
|
|
|
*/ |
469
|
|
|
public function getUserById(ObjectId $id): User |
470
|
|
|
{ |
471
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
472
|
|
|
array_unshift($aggregation, ['$match' => ['_id' => $id]]); |
473
|
|
|
$users = $this->db->user->aggregate($aggregation)->toArray(); |
474
|
|
|
|
475
|
|
|
if (count($users) > 1) { |
476
|
|
|
throw new User\Exception\NotUnique('multiple user found'); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
if (count($users) === 0) { |
480
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
return new User(array_shift($users), $this, $this->db, $this->logger); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Get users by id. |
488
|
|
|
* |
489
|
|
|
* @param array $id |
490
|
|
|
* |
491
|
|
|
* @return Generator |
492
|
|
|
*/ |
493
|
|
|
public function getUsersById(array $id): Generator |
494
|
|
|
{ |
495
|
|
|
$find = []; |
496
|
|
|
foreach ($id as $i) { |
497
|
|
|
$find[] = new ObjectId($i); |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
$filter = [ |
501
|
|
|
'$match' => [ |
502
|
|
|
'_id' => ['$in' => $find], |
503
|
|
|
], |
504
|
|
|
]; |
505
|
|
|
|
506
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
507
|
|
|
array_unshift($aggregation, $filter); |
508
|
|
|
$users = $this->db->user->aggregate($aggregation); |
509
|
|
|
|
510
|
|
|
foreach ($users as $attributes) { |
511
|
|
|
yield new User($attributes, $this, $this->db, $this->logger); |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
/** |
516
|
|
|
* Set Identity. |
517
|
|
|
* |
518
|
|
|
* @param Identity $identity |
519
|
|
|
* |
520
|
|
|
* @return bool |
521
|
|
|
*/ |
522
|
|
|
public function setIdentity(Identity $identity): bool |
523
|
|
|
{ |
524
|
|
|
$user = null; |
525
|
|
|
|
526
|
|
|
try { |
527
|
|
|
$user = $this->getUserByName($identity->getIdentifier()); |
528
|
|
|
} catch (User\Exception\NotFound $e) { |
529
|
|
|
$this->logger->warning('failed connect authenticated user, user account does not exists', [ |
530
|
|
|
'category' => get_class($this), |
531
|
|
|
]); |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
$this->hook->run('preServerIdentity', [$identity, &$user]); |
535
|
|
|
|
536
|
|
|
if (!($user instanceof User)) { |
537
|
|
|
throw new User\Exception\NotAuthenticated('user does not exists', User\Exception\NotAuthenticated::USER_NOT_FOUND); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
if ($user->isDeleted()) { |
541
|
|
|
throw new User\Exception\NotAuthenticated( |
542
|
|
|
'user is disabled and can not be used', |
543
|
|
|
User\Exception\NotAuthenticated::USER_DELETED |
544
|
|
|
); |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
$this->identity = $user; |
548
|
|
|
$user->updateIdentity($identity) |
549
|
|
|
->updateShares(); |
550
|
|
|
$this->hook->run('postServerIdentity', [$user]); |
551
|
|
|
|
552
|
|
|
return true; |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* Get authenticated user. |
557
|
|
|
* |
558
|
|
|
* @return User |
559
|
|
|
*/ |
560
|
|
|
public function getIdentity(): ?User |
561
|
|
|
{ |
562
|
|
|
return $this->identity; |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* Get user by name. |
567
|
|
|
* |
568
|
|
|
* @param string $name |
569
|
|
|
* |
570
|
|
|
* @return User |
571
|
|
|
*/ |
572
|
|
|
public function getUserByName(string $name): User |
573
|
|
|
{ |
574
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
575
|
|
|
array_unshift($aggregation, ['$match' => ['username' => $name]]); |
576
|
|
|
$users = $this->db->user->aggregate($aggregation)->toArray(); |
577
|
|
|
|
578
|
|
|
if (count($users) > 1) { |
579
|
|
|
throw new User\Exception\NotUnique('multiple user found'); |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
if (count($users) === 0) { |
583
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
return new User(array_shift($users), $this, $this->db, $this->logger); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Count users. |
591
|
|
|
* |
592
|
|
|
* @param array $filter |
593
|
|
|
* |
594
|
|
|
* @return int |
595
|
|
|
*/ |
596
|
|
|
public function countUsers(array $filter): int |
597
|
|
|
{ |
598
|
|
|
return $this->db->user->count($filter); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Count groups. |
603
|
|
|
* |
604
|
|
|
* @param array $filter |
605
|
|
|
* |
606
|
|
|
* @return int |
607
|
|
|
*/ |
608
|
|
|
public function countGroups(array $filter): int |
609
|
|
|
{ |
610
|
|
|
return $this->db->group->count($filter); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
/** |
614
|
|
|
* Get users. |
615
|
|
|
* |
616
|
|
|
* @param array $filter |
617
|
|
|
* @param int $offset |
618
|
|
|
* @param int $limit |
619
|
|
|
* |
620
|
|
|
* @return Generator |
621
|
|
|
*/ |
622
|
|
|
public function getUsers(array $filter, ?int $offset = null, ?int $limit = null): Generator |
623
|
|
|
{ |
624
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
625
|
|
|
|
626
|
|
|
if (count($filter) > 0) { |
627
|
|
|
array_unshift($aggregation, ['$match' => $filter]); |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
if ($offset !== null) { |
631
|
|
|
array_unshift($aggregation, ['$skip' => $offset]); |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
if ($limit !== null) { |
635
|
|
|
$aggregation[] = ['$limit' => $limit]; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
$users = $this->db->user->aggregate($aggregation); |
639
|
|
|
|
640
|
|
|
foreach ($users as $attributes) { |
641
|
|
|
yield new User($attributes, $this, $this->db, $this->logger); |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
return $this->db->user->count($filter); |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Get groups. |
649
|
|
|
* |
650
|
|
|
* @param array $filter |
651
|
|
|
* @param int $offset |
652
|
|
|
* @param int $limit |
653
|
|
|
* |
654
|
|
|
* @return Generator |
655
|
|
|
*/ |
656
|
|
|
public function getGroups(array $filter, ?int $offset = null, ?int $limit = null): Generator |
657
|
|
|
{ |
658
|
|
|
$groups = $this->db->group->find($filter, [ |
659
|
|
|
'skip' => $offset, |
660
|
|
|
'limit' => $limit, |
661
|
|
|
]); |
662
|
|
|
|
663
|
|
|
foreach ($groups as $attributes) { |
664
|
|
|
yield new Group($attributes, $this, $this->db, $this->logger); |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
return $this->db->group->count($filter); |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
/** |
671
|
|
|
* Get group by name. |
672
|
|
|
* |
673
|
|
|
* @param string $name |
674
|
|
|
* |
675
|
|
|
* @return Group |
676
|
|
|
*/ |
677
|
|
|
public function getGroupByName(string $name): Group |
678
|
|
|
{ |
679
|
|
|
$group = $this->db->group->findOne([ |
680
|
|
|
'name' => $name, |
681
|
|
|
]); |
682
|
|
|
|
683
|
|
|
if (null === $group) { |
684
|
|
|
throw new Group\Exception\NotFound('group does not exists'); |
685
|
|
|
} |
686
|
|
|
|
687
|
|
|
return new Group($group, $this, $this->db, $this->logger); |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* Get group by id. |
692
|
|
|
* |
693
|
|
|
* @param string $id |
694
|
|
|
* |
695
|
|
|
* @return Group |
696
|
|
|
*/ |
697
|
|
|
public function getGroupById(ObjectId $id): Group |
698
|
|
|
{ |
699
|
|
|
$group = $this->db->group->findOne([ |
700
|
|
|
'_id' => $id, |
701
|
|
|
]); |
702
|
|
|
|
703
|
|
|
if (null === $group) { |
704
|
|
|
throw new Group\Exception\NotFound('group does not exists'); |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
return new Group($group, $this, $this->db, $this->logger); |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Add group. |
712
|
|
|
* |
713
|
|
|
* @param string $name |
714
|
|
|
* @param array $member |
715
|
|
|
* @param array $attributes |
716
|
|
|
* |
717
|
|
|
* @return ObjectId |
718
|
|
|
*/ |
719
|
|
|
public function addGroup(string $name, array $member = [], array $attributes = []): ObjectId |
720
|
|
|
{ |
721
|
|
|
$attributes['member'] = $member; |
722
|
|
|
$attributes['name'] = $name; |
723
|
|
|
$attributes = $this->validateGroupAttributes($attributes); |
724
|
|
|
|
725
|
|
|
$defaults = [ |
726
|
|
|
'created' => new UTCDateTime(), |
727
|
|
|
'changed' => new UTCDateTime(), |
728
|
|
|
'deleted' => false, |
729
|
|
|
]; |
730
|
|
|
|
731
|
|
|
$attributes = array_merge($attributes, $defaults); |
732
|
|
|
$result = $this->db->group->insertOne($attributes); |
733
|
|
|
|
734
|
|
|
return $result->getInsertedId(); |
735
|
|
|
} |
736
|
|
|
|
737
|
|
|
/** |
738
|
|
|
* Get user aggregation pipe. |
739
|
|
|
* |
740
|
|
|
* @return array |
741
|
|
|
*/ |
742
|
|
|
protected function getUserAggregationPipes(): array |
743
|
|
|
{ |
744
|
|
|
return [ |
745
|
|
|
['$lookup' => [ |
746
|
|
|
'from' => 'group', |
747
|
|
|
'localField' => '_id', |
748
|
|
|
'foreignField' => 'member', |
749
|
|
|
'as' => 'groups', |
750
|
|
|
]], |
751
|
|
|
['$addFields' => [ |
752
|
|
|
'groups' => [ |
753
|
|
|
'$map' => [ |
754
|
|
|
'input' => '$groups', |
755
|
|
|
'as' => 'group', |
756
|
|
|
'in' => '$$group._id', |
757
|
|
|
], |
758
|
|
|
], |
759
|
|
|
]], |
760
|
|
|
]; |
761
|
|
|
} |
762
|
|
|
} |
763
|
|
|
|
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.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.