|
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 = 16; |
|
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 iterable $config |
|
117
|
|
|
*/ |
|
118
|
|
|
public function __construct(Database $db, Storage $storage, LoggerInterface $logger, Hook $hook, Acl $acl, ?Iterable $config = null) |
|
119
|
|
|
{ |
|
120
|
|
|
$this->db = $db; |
|
121
|
|
|
$this->storage = $storage; |
|
122
|
|
|
$this->logger = $logger; |
|
123
|
|
|
$this->hook = $hook; |
|
124
|
|
|
$this->acl = $acl; |
|
125
|
|
|
|
|
126
|
|
|
$this->setOptions($config); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Set options. |
|
131
|
|
|
* |
|
132
|
|
|
* @param iterable $config |
|
133
|
|
|
* |
|
134
|
|
|
* @return Server |
|
135
|
|
|
*/ |
|
136
|
|
|
public function setOptions(?Iterable $config = null): self |
|
137
|
|
|
{ |
|
138
|
|
|
if (null === $config) { |
|
139
|
|
|
return $this; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
foreach ($config as $name => $value) { |
|
143
|
|
|
switch ($name) { |
|
144
|
|
|
case 'temp_dir': |
|
|
|
|
|
|
145
|
|
|
case 'password_policy': |
|
146
|
|
|
case 'server_url': |
|
147
|
|
|
$this->{$name} = (string) $value; |
|
148
|
|
|
|
|
149
|
|
|
break; |
|
150
|
|
|
case 'max_file_version': |
|
151
|
|
|
case 'max_file_size': |
|
152
|
|
|
case 'password_hash': |
|
153
|
|
|
$this->{$name} = (int) $value; |
|
154
|
|
|
|
|
155
|
|
|
break; |
|
156
|
|
|
default: |
|
157
|
|
|
throw new InvalidArgumentException('invalid option '.$name.' given'); |
|
158
|
|
|
} |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
return $this; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* Get server url. |
|
166
|
|
|
*/ |
|
167
|
|
|
public function getServerUrl(): string |
|
168
|
|
|
{ |
|
169
|
|
|
return $this->server_url; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* Get temporary directory. |
|
174
|
|
|
*/ |
|
175
|
|
|
public function getTempDir(): string |
|
176
|
|
|
{ |
|
177
|
|
|
return $this->temp_dir; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* Get max file version. |
|
182
|
|
|
*/ |
|
183
|
|
|
public function getMaxFileVersion(): int |
|
184
|
|
|
{ |
|
185
|
|
|
return $this->max_file_version; |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
/** |
|
189
|
|
|
* Get max file size. |
|
190
|
|
|
*/ |
|
191
|
|
|
public function getMaxFileSize(): int |
|
192
|
|
|
{ |
|
193
|
|
|
return $this->max_file_size; |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* Filesystem factory. |
|
198
|
|
|
*/ |
|
199
|
|
|
public function getFilesystem(?User $user = null): Filesystem |
|
200
|
|
|
{ |
|
201
|
|
|
if (null !== $user) { |
|
202
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $user); |
|
203
|
|
|
} |
|
204
|
|
|
if ($this->identity instanceof User) { |
|
205
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl, $this->identity); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
return new Filesystem($this, $this->db, $this->hook, $this->logger, $this->storage, $this->acl); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* Verify group attributes. |
|
213
|
|
|
*/ |
|
214
|
|
|
public function validateGroupAttributes(array $attributes): array |
|
215
|
|
|
{ |
|
216
|
|
|
foreach ($attributes as $attribute => &$value) { |
|
217
|
|
|
switch ($attribute) { |
|
218
|
|
|
case 'namespace': |
|
219
|
|
|
if (!is_string($value)) { |
|
220
|
|
|
throw new Group\Exception\InvalidArgument( |
|
221
|
|
|
$attribute.' must be a valid string', |
|
222
|
|
|
Group\Exception\InvalidArgument::INVALID_NAMESPACE |
|
223
|
|
|
); |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
break; |
|
227
|
|
|
case 'name': |
|
228
|
|
|
if (!is_string($value)) { |
|
229
|
|
|
throw new Group\Exception\InvalidArgument( |
|
230
|
|
|
$attribute.' must be a valid string', |
|
231
|
|
|
Group\Exception\InvalidArgument::INVALID_NAME |
|
232
|
|
|
); |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
break; |
|
236
|
|
|
case 'optional': |
|
237
|
|
|
if (!is_array($value)) { |
|
238
|
|
|
throw new Group\Exception\InvalidArgument( |
|
239
|
|
|
'optional group attributes must be an array', |
|
240
|
|
|
Group\Exception\InvalidArgument::INVALID_OPTIONAL |
|
241
|
|
|
); |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
break; |
|
245
|
|
|
case 'member': |
|
246
|
|
|
if (!is_array($value)) { |
|
247
|
|
|
throw new Group\Exception\InvalidArgument( |
|
248
|
|
|
'member must be an array of user', |
|
249
|
|
|
Group\Exception\InvalidArgument::INVALID_MEMBER |
|
250
|
|
|
); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
$valid = []; |
|
254
|
|
|
foreach ($value as $id) { |
|
255
|
|
|
if ($id instanceof User) { |
|
256
|
|
|
$id = $id->getId(); |
|
257
|
|
|
} else { |
|
258
|
|
|
$id = new ObjectId($id); |
|
259
|
|
|
if (!$this->userExists($id)) { |
|
260
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
|
261
|
|
|
} |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
if (!in_array($id, $valid)) { |
|
265
|
|
|
$valid[] = $id; |
|
266
|
|
|
} |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
$value = $valid; |
|
270
|
|
|
|
|
271
|
|
|
break; |
|
272
|
|
|
default: |
|
273
|
|
|
throw new Group\Exception\InvalidArgument( |
|
274
|
|
|
'invalid attribute '.$attribute.' given', |
|
275
|
|
|
Group\Exception\InvalidArgument::INVALID_ATTRIBUTE |
|
276
|
|
|
); |
|
277
|
|
|
} |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
return $attributes; |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* Verify user attributes. |
|
285
|
|
|
*/ |
|
286
|
|
|
public function validateUserAttributes(array $attributes): array |
|
287
|
|
|
{ |
|
288
|
|
|
foreach ($attributes as $attribute => &$value) { |
|
289
|
|
|
switch ($attribute) { |
|
290
|
|
|
case 'username': |
|
291
|
|
|
if (!preg_match('/^[A-Za-z0-9\.-_\@]+$/', $value)) { |
|
292
|
|
|
throw new User\Exception\InvalidArgument( |
|
293
|
|
|
'username does not match required regex /^[A-Za-z0-9\.-_\@]+$/', |
|
294
|
|
|
User\Exception\InvalidArgument::INVALID_USERNAME |
|
295
|
|
|
); |
|
296
|
|
|
} |
|
297
|
|
|
|
|
298
|
|
|
if ($this->usernameExists($value)) { |
|
299
|
|
|
throw new User\Exception\NotUnique('user does already exists'); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
break; |
|
303
|
|
|
case 'password': |
|
304
|
|
|
if (!preg_match($this->password_policy, $value)) { |
|
305
|
|
|
throw new User\Exception\InvalidArgument( |
|
306
|
|
|
'password does not follow password policy '.$this->password_policy, |
|
307
|
|
|
User\Exception\InvalidArgument::INVALID_PASSWORD |
|
308
|
|
|
); |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
$value = password_hash($value, $this->password_hash); |
|
312
|
|
|
|
|
313
|
|
|
break; |
|
314
|
|
|
case 'soft_quota': |
|
315
|
|
|
case 'hard_quota': |
|
316
|
|
|
if (!is_numeric($value)) { |
|
317
|
|
|
throw new User\Exception\InvalidArgument( |
|
318
|
|
|
$attribute.' must be numeric', |
|
319
|
|
|
User\Exception\InvalidArgument::INVALID_QUOTA |
|
320
|
|
|
); |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
break; |
|
324
|
|
|
case 'avatar': |
|
325
|
|
|
if (!$value instanceof Binary) { |
|
|
|
|
|
|
326
|
|
|
throw new User\Exception\InvalidArgument( |
|
327
|
|
|
'avatar must be an instance of Binary', |
|
328
|
|
|
User\Exception\InvalidArgument::INVALID_AVATAR |
|
329
|
|
|
); |
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
break; |
|
333
|
|
|
case 'mail': |
|
334
|
|
|
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { |
|
335
|
|
|
throw new User\Exception\InvalidArgument( |
|
336
|
|
|
'mail address given is invalid', |
|
337
|
|
|
User\Exception\InvalidArgument::INVALID_MAIL |
|
338
|
|
|
); |
|
339
|
|
|
} |
|
340
|
|
|
|
|
341
|
|
|
break; |
|
342
|
|
|
case 'admin': |
|
343
|
|
|
$value = (bool) $value; |
|
344
|
|
|
|
|
345
|
|
|
break; |
|
346
|
|
|
case 'locale': |
|
347
|
|
|
if (!preg_match('#^[a-z]{2}_[A-Z]{2}$#', $value)) { |
|
348
|
|
|
throw new User\Exception\InvalidArgument( |
|
349
|
|
|
'invalid locale given, must be according to format a-z_A-Z', |
|
350
|
|
|
User\Exception\InvalidArgument::INVALID_LOCALE |
|
351
|
|
|
); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
break; |
|
355
|
|
|
case 'namespace': |
|
356
|
|
|
if (!is_string($value)) { |
|
357
|
|
|
throw new User\Exception\InvalidArgument( |
|
358
|
|
|
'namespace must be a valid string', |
|
359
|
|
|
User\Exception\InvalidArgument::INVALID_NAMESPACE |
|
360
|
|
|
); |
|
361
|
|
|
} |
|
362
|
|
|
|
|
363
|
|
|
break; |
|
364
|
|
|
case 'optional': |
|
365
|
|
|
if (!is_array($value)) { |
|
366
|
|
|
throw new User\Exception\InvalidArgument( |
|
367
|
|
|
'optional user attributes must be an array', |
|
368
|
|
|
User\Exception\InvalidArgument::INVALID_OPTIONAL |
|
369
|
|
|
); |
|
370
|
|
|
} |
|
371
|
|
|
|
|
372
|
|
|
break; |
|
373
|
|
|
default: |
|
374
|
|
|
throw new User\Exception\InvalidArgument( |
|
375
|
|
|
'invalid attribute '.$attribute.' given', |
|
376
|
|
|
User\Exception\InvalidArgument::INVALID_ATTRIBUTE |
|
377
|
|
|
); |
|
378
|
|
|
} |
|
379
|
|
|
} |
|
380
|
|
|
|
|
381
|
|
|
return $attributes; |
|
382
|
|
|
} |
|
383
|
|
|
|
|
384
|
|
|
/** |
|
385
|
|
|
* Add user. |
|
386
|
|
|
*/ |
|
387
|
|
|
public function addUser(string $username, array $attributes = []): ObjectId |
|
388
|
|
|
{ |
|
389
|
|
|
$attributes['username'] = $username; |
|
390
|
|
|
$attributes = $this->validateUserAttributes($attributes); |
|
391
|
|
|
|
|
392
|
|
|
$defaults = [ |
|
393
|
|
|
'created' => new UTCDateTime(), |
|
394
|
|
|
'changed' => new UTCDateTime(), |
|
395
|
|
|
'deleted' => false, |
|
396
|
|
|
]; |
|
397
|
|
|
|
|
398
|
|
|
$attributes = array_merge($defaults, $attributes); |
|
399
|
|
|
$result = $this->db->user->insertOne($attributes); |
|
400
|
|
|
|
|
401
|
|
|
return $result->getInsertedId(); |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
/** |
|
405
|
|
|
* Check if user exists. |
|
406
|
|
|
*/ |
|
407
|
|
|
public function usernameExists(string $username): bool |
|
408
|
|
|
{ |
|
409
|
|
|
return 1 === $this->db->user->count(['username' => $username]); |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
/** |
|
413
|
|
|
* Check if user exists. |
|
414
|
|
|
*/ |
|
415
|
|
|
public function userExists(ObjectId $id): bool |
|
416
|
|
|
{ |
|
417
|
|
|
return 1 === $this->db->user->count(['_id' => $id]); |
|
418
|
|
|
} |
|
419
|
|
|
|
|
420
|
|
|
/** |
|
421
|
|
|
* Check if user exists. |
|
422
|
|
|
*/ |
|
423
|
|
|
public function groupExists(ObjectId $id): bool |
|
424
|
|
|
{ |
|
425
|
|
|
return 1 === $this->db->group->count(['_id' => $id]); |
|
426
|
|
|
} |
|
427
|
|
|
|
|
428
|
|
|
/** |
|
429
|
|
|
* Get user by id. |
|
430
|
|
|
*/ |
|
431
|
|
|
public function getUserById(ObjectId $id): User |
|
432
|
|
|
{ |
|
433
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
|
434
|
|
|
array_unshift($aggregation, ['$match' => ['_id' => $id]]); |
|
435
|
|
|
$users = $this->db->user->aggregate($aggregation)->toArray(); |
|
436
|
|
|
|
|
437
|
|
|
if (count($users) > 1) { |
|
438
|
|
|
throw new User\Exception\NotUnique('multiple user found'); |
|
439
|
|
|
} |
|
440
|
|
|
|
|
441
|
|
|
if (count($users) === 0) { |
|
442
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
|
443
|
|
|
} |
|
444
|
|
|
|
|
445
|
|
|
return new User(array_shift($users), $this, $this->db, $this->logger); |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
/** |
|
449
|
|
|
* Get users by id. |
|
450
|
|
|
*/ |
|
451
|
|
|
public function getUsersById(array $id): Generator |
|
452
|
|
|
{ |
|
453
|
|
|
$find = []; |
|
454
|
|
|
foreach ($id as $i) { |
|
455
|
|
|
$find[] = new ObjectId($i); |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
$filter = [ |
|
459
|
|
|
'$match' => [ |
|
460
|
|
|
'_id' => ['$in' => $find], |
|
461
|
|
|
], |
|
462
|
|
|
]; |
|
463
|
|
|
|
|
464
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
|
465
|
|
|
array_unshift($aggregation, $filter); |
|
466
|
|
|
$users = $this->db->user->aggregate($aggregation); |
|
467
|
|
|
|
|
468
|
|
|
foreach ($users as $attributes) { |
|
469
|
|
|
yield new User($attributes, $this, $this->db, $this->logger); |
|
470
|
|
|
} |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
/** |
|
474
|
|
|
* Set Identity. |
|
475
|
|
|
*/ |
|
476
|
|
|
public function setIdentity(Identity $identity): bool |
|
477
|
|
|
{ |
|
478
|
|
|
$user = null; |
|
479
|
|
|
|
|
480
|
|
|
try { |
|
481
|
|
|
$user = $this->getUserByName($identity->getIdentifier()); |
|
482
|
|
|
} catch (User\Exception\NotFound $e) { |
|
483
|
|
|
$this->logger->warning('failed connect authenticated user, user account does not exists', [ |
|
484
|
|
|
'category' => get_class($this), |
|
485
|
|
|
]); |
|
486
|
|
|
} |
|
487
|
|
|
|
|
488
|
|
|
$this->hook->run('preServerIdentity', [$identity, &$user]); |
|
489
|
|
|
|
|
490
|
|
|
if (!($user instanceof User)) { |
|
491
|
|
|
throw new User\Exception\NotAuthenticated('user does not exists', User\Exception\NotAuthenticated::USER_NOT_FOUND); |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
if ($user->isDeleted()) { |
|
495
|
|
|
throw new User\Exception\NotAuthenticated( |
|
496
|
|
|
'user is disabled and can not be used', |
|
497
|
|
|
User\Exception\NotAuthenticated::USER_DELETED |
|
498
|
|
|
); |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
$this->identity = $user; |
|
502
|
|
|
$user->updateIdentity($identity) |
|
503
|
|
|
->updateShares(); |
|
504
|
|
|
$this->hook->run('postServerIdentity', [$user]); |
|
505
|
|
|
|
|
506
|
|
|
return true; |
|
507
|
|
|
} |
|
508
|
|
|
|
|
509
|
|
|
/** |
|
510
|
|
|
* Get authenticated user. |
|
511
|
|
|
* |
|
512
|
|
|
* @return User |
|
513
|
|
|
*/ |
|
514
|
|
|
public function getIdentity(): ?User |
|
515
|
|
|
{ |
|
516
|
|
|
return $this->identity; |
|
517
|
|
|
} |
|
518
|
|
|
|
|
519
|
|
|
/** |
|
520
|
|
|
* Get user by name. |
|
521
|
|
|
*/ |
|
522
|
|
|
public function getUserByName(string $name): User |
|
523
|
|
|
{ |
|
524
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
|
525
|
|
|
array_unshift($aggregation, ['$match' => ['username' => $name]]); |
|
526
|
|
|
$users = $this->db->user->aggregate($aggregation)->toArray(); |
|
527
|
|
|
|
|
528
|
|
|
if (count($users) > 1) { |
|
529
|
|
|
throw new User\Exception\NotUnique('multiple user found'); |
|
530
|
|
|
} |
|
531
|
|
|
|
|
532
|
|
|
if (count($users) === 0) { |
|
533
|
|
|
throw new User\Exception\NotFound('user does not exists'); |
|
534
|
|
|
} |
|
535
|
|
|
|
|
536
|
|
|
return new User(array_shift($users), $this, $this->db, $this->logger); |
|
537
|
|
|
} |
|
538
|
|
|
|
|
539
|
|
|
/** |
|
540
|
|
|
* Count users. |
|
541
|
|
|
*/ |
|
542
|
|
|
public function countUsers(array $filter): int |
|
543
|
|
|
{ |
|
544
|
|
|
return $this->db->user->count($filter); |
|
545
|
|
|
} |
|
546
|
|
|
|
|
547
|
|
|
/** |
|
548
|
|
|
* Count groups. |
|
549
|
|
|
*/ |
|
550
|
|
|
public function countGroups(array $filter): int |
|
551
|
|
|
{ |
|
552
|
|
|
return $this->db->group->count($filter); |
|
553
|
|
|
} |
|
554
|
|
|
|
|
555
|
|
|
/** |
|
556
|
|
|
* Get users. |
|
557
|
|
|
* |
|
558
|
|
|
* @param int $offset |
|
559
|
|
|
* @param int $limit |
|
560
|
|
|
*/ |
|
561
|
|
|
public function getUsers(array $filter = [], ?int $offset = null, ?int $limit = null): Generator |
|
562
|
|
|
{ |
|
563
|
|
|
$aggregation = $this->getUserAggregationPipes(); |
|
564
|
|
|
|
|
565
|
|
|
if (count($filter) > 0) { |
|
566
|
|
|
array_unshift($aggregation, ['$match' => $filter]); |
|
567
|
|
|
} |
|
568
|
|
|
|
|
569
|
|
|
if ($offset !== null) { |
|
570
|
|
|
array_unshift($aggregation, ['$skip' => $offset]); |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
if ($limit !== null) { |
|
574
|
|
|
$aggregation[] = ['$limit' => $limit]; |
|
575
|
|
|
} |
|
576
|
|
|
|
|
577
|
|
|
$users = $this->db->user->aggregate($aggregation); |
|
578
|
|
|
|
|
579
|
|
|
foreach ($users as $attributes) { |
|
580
|
|
|
yield new User($attributes, $this, $this->db, $this->logger); |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
return $this->db->user->count($filter); |
|
584
|
|
|
} |
|
585
|
|
|
|
|
586
|
|
|
/** |
|
587
|
|
|
* Get groups. |
|
588
|
|
|
* |
|
589
|
|
|
* @param int $offset |
|
590
|
|
|
* @param int $limit |
|
591
|
|
|
*/ |
|
592
|
|
|
public function getGroups(array $filter = [], ?int $offset = null, ?int $limit = null): Generator |
|
593
|
|
|
{ |
|
594
|
|
|
$groups = $this->db->group->find($filter, [ |
|
595
|
|
|
'skip' => $offset, |
|
596
|
|
|
'limit' => $limit, |
|
597
|
|
|
]); |
|
598
|
|
|
|
|
599
|
|
|
foreach ($groups as $attributes) { |
|
600
|
|
|
yield new Group($attributes, $this, $this->db, $this->logger); |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
return $this->db->group->count($filter); |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
|
|
/** |
|
607
|
|
|
* Get group by name. |
|
608
|
|
|
*/ |
|
609
|
|
|
public function getGroupByName(string $name): Group |
|
610
|
|
|
{ |
|
611
|
|
|
$group = $this->db->group->findOne([ |
|
612
|
|
|
'name' => $name, |
|
613
|
|
|
]); |
|
614
|
|
|
|
|
615
|
|
|
if (null === $group) { |
|
616
|
|
|
throw new Group\Exception\NotFound('group does not exists'); |
|
617
|
|
|
} |
|
618
|
|
|
|
|
619
|
|
|
return new Group($group, $this, $this->db, $this->logger); |
|
620
|
|
|
} |
|
621
|
|
|
|
|
622
|
|
|
/** |
|
623
|
|
|
* Get group by id. |
|
624
|
|
|
* |
|
625
|
|
|
* @param string $id |
|
626
|
|
|
*/ |
|
627
|
|
|
public function getGroupById(ObjectId $id): Group |
|
628
|
|
|
{ |
|
629
|
|
|
$group = $this->db->group->findOne([ |
|
630
|
|
|
'_id' => $id, |
|
631
|
|
|
]); |
|
632
|
|
|
|
|
633
|
|
|
if (null === $group) { |
|
634
|
|
|
throw new Group\Exception\NotFound('group does not exists'); |
|
635
|
|
|
} |
|
636
|
|
|
|
|
637
|
|
|
return new Group($group, $this, $this->db, $this->logger); |
|
638
|
|
|
} |
|
639
|
|
|
|
|
640
|
|
|
/** |
|
641
|
|
|
* Add group. |
|
642
|
|
|
*/ |
|
643
|
|
|
public function addGroup(string $name, array $member = [], array $attributes = []): ObjectId |
|
644
|
|
|
{ |
|
645
|
|
|
$attributes['member'] = $member; |
|
646
|
|
|
$attributes['name'] = $name; |
|
647
|
|
|
$attributes = $this->validateGroupAttributes($attributes); |
|
648
|
|
|
|
|
649
|
|
|
$defaults = [ |
|
650
|
|
|
'created' => new UTCDateTime(), |
|
651
|
|
|
'changed' => new UTCDateTime(), |
|
652
|
|
|
'deleted' => false, |
|
653
|
|
|
]; |
|
654
|
|
|
|
|
655
|
|
|
$attributes = array_merge($attributes, $defaults); |
|
656
|
|
|
$result = $this->db->group->insertOne($attributes); |
|
657
|
|
|
|
|
658
|
|
|
return $result->getInsertedId(); |
|
659
|
|
|
} |
|
660
|
|
|
|
|
661
|
|
|
/** |
|
662
|
|
|
* Get user aggregation pipe. |
|
663
|
|
|
*/ |
|
664
|
|
|
protected function getUserAggregationPipes(): array |
|
665
|
|
|
{ |
|
666
|
|
|
return [ |
|
667
|
|
|
['$lookup' => [ |
|
668
|
|
|
'from' => 'group', |
|
669
|
|
|
'localField' => '_id', |
|
670
|
|
|
'foreignField' => 'member', |
|
671
|
|
|
'as' => 'groups', |
|
672
|
|
|
]], |
|
673
|
|
|
['$addFields' => [ |
|
674
|
|
|
'groups' => [ |
|
675
|
|
|
'$map' => [ |
|
676
|
|
|
'input' => '$groups', |
|
677
|
|
|
'as' => 'group', |
|
678
|
|
|
'in' => '$$group._id', |
|
679
|
|
|
], |
|
680
|
|
|
], |
|
681
|
|
|
]], |
|
682
|
|
|
]; |
|
683
|
|
|
} |
|
684
|
|
|
} |
|
685
|
|
|
|
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.