Passed
Push — master ( c98abf...91bafd )
by Jonathan
19:18
created

User::addRecursiveChildrenGroups()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 22
rs 8.8333
cc 7
nc 10
nop 4
1
<?php
2
3
namespace Uccello\Core\Models;
4
5
use Illuminate\Notifications\Notifiable;
0 ignored issues
show
Bug introduced by
The type Illuminate\Notifications\Notifiable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Illuminate\Foundation\Auth\User as Authenticatable;
0 ignored issues
show
Bug introduced by
The type Illuminate\Foundation\Auth\User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Illuminate\Database\Eloquent\SoftDeletes;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\SoftDeletes was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Illuminate\Support\Collection;
0 ignored issues
show
Bug introduced by
The type Illuminate\Support\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Illuminate\Support\Facades\Cache;
0 ignored issues
show
Bug introduced by
The type Illuminate\Support\Facades\Cache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Spatie\Searchable\Searchable;
0 ignored issues
show
Bug introduced by
The type Spatie\Searchable\Searchable was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use Spatie\Searchable\SearchResult;
0 ignored issues
show
Bug introduced by
The type Spatie\Searchable\SearchResult was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Uccello\Core\Support\Traits\RelatedlistTrait;
13
use Uccello\Core\Support\Traits\UccelloModule;
14
use Uccello\Core\Models\Group;
15
16
class User extends Authenticatable implements Searchable
17
{
18
    use SoftDeletes;
19
    use Notifiable;
20
    use RelatedlistTrait;
21
    use UccelloModule;
22
23
    /**
24
     * The table associated with the model.
25
     *
26
     * @var string
27
     */
28
    protected $table = 'users';
29
30
    /**
31
     * The attributes that should be mutated to dates.
32
     *
33
     * @var array
34
     */
35
    protected $dates = [ 'deleted_at' ];
36
37
    /**
38
     * The attributes that should be casted to native types.
39
     *
40
     * @var array
41
     */
42
    protected $casts = [
43
        'avatar' => 'object',
44
    ];
45
46
    /**
47
     * The attributes that are mass assignable.
48
     *
49
     * @var array
50
     */
51
    protected $fillable = [
52
        'username',
53
        'name',
54
        'email',
55
        'password',
56
        'is_admin',
57
        'domain_id'
58
    ];
59
60
    /**
61
     * The attributes that should be hidden for arrays.
62
     *
63
     * @var array
64
     */
65
    protected $hidden = [
66
        'password', 'remember_token',
67
    ];
68
69
    /**
70
     * The accessors to append to the model's array form.
71
     *
72
     * @var array
73
     */
74
    protected $appends = [
75
        'recordLabel'
76
    ];
77
78
    public $searchableType = 'user';
79
80
    public $searchableColumns = [
81
        'name'
82
    ];
83
84
    public function getSearchResult(): SearchResult
85
    {
86
        return new SearchResult(
87
            $this,
88
            $this->recordLabel
89
        );
90
    }
91
92
    public function domain()
93
    {
94
        return $this->belongsTo(Domain::class);
95
    }
96
97
    public function lastDomain()
98
    {
99
        return $this->belongsTo(Domain::class);
100
    }
101
102
    public function privileges()
103
    {
104
        return $this->hasMany(Privilege::class);
105
    }
106
107
    public function menus()
108
    {
109
        return $this->hasMany(Menu::class);
110
    }
111
112
    public function groups()
113
    {
114
        return $this->belongsToMany(Group::class, 'uccello_rl_groups_users');
115
    }
116
117
    /**
118
     * Returns record label
119
     *
120
     * @return string
121
     */
122
    public function getRecordLabelAttribute() : string
123
    {
124
        return trim($this->name) ?? $this->username;
125
    }
126
127
    /**
128
     * Get avatar type
129
     *
130
     * @return string
131
     */
132
    public function getAvatarTypeAttribute() : string
133
    {
134
        return $this->avatar->type ?? 'initials';
135
    }
136
137
    /**
138
     * Returns initals generated from the user name
139
     *
140
     * @return string
141
     */
142
    public function getInitialsAttribute() : string
143
    {
144
        $initials = "";
145
146
        $words = explode(" ", strtoupper($this->name));
147
148
        $i = 0;
149
        foreach ($words as $w) {
150
            $initials .= $w[0];
151
            $i++;
152
153
            if ($i === 3) { // Maximum: 3 letters
154
                break;
155
            }
156
        }
157
158
        return $initials;
159
    }
160
161
    /**
162
     * Returns the image to use as the user avatar
163
     *
164
     * @return string
165
     */
166
    public function getImageAttribute() : string
167
    {
168
        $image = 'vendor/uccello/uccello/images/user-no-image.png';
169
170
        if ($this->avatarType === 'gravatar') {
171
            $image = 'https://www.gravatar.com/avatar/' . md5($this->email) . '?d=mm';
172
173
        } elseif ($this->avatarType === 'image' && !empty($this->avatar->path)) {
174
            $image = $this->avatar->path;
175
        }
176
177
        return $image;
178
    }
179
180
    /**
181
     * Returns user's roles on a domain
182
     *
183
     * @param \Uccello\Core\Models\Domain $domain
184
     * @return \Illuminate\Support\Collection
185
     */
186
    public function rolesOnDomain($domain) : Collection
187
    {
188
        return Cache::remember('domain_'.$domain->slug.'_roles', 600, function () use($domain) {
189
            $roles = collect();
0 ignored issues
show
Bug introduced by
The function collect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

189
            $roles = /** @scrutinizer ignore-call */ collect();
Loading history...
190
191
            if (config('uccello.roles.display_ancestors_roles')) {
0 ignored issues
show
Bug introduced by
The function config was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

191
            if (/** @scrutinizer ignore-call */ config('uccello.roles.display_ancestors_roles')) {
Loading history...
192
                $treeDomainsIds = $domain->findAncestors()->pluck('id');
193
            } else {
194
                $treeDomainsIds = collect([ $domain->id ]);
195
            }
196
197
            foreach ($treeDomainsIds as $treeDomainId) {
198
                $_domain = Domain::find($treeDomainId);
199
                foreach ($this->privileges->where('domain_id', $_domain->id) as $privilege) {
200
                    $roles[ ] = $privilege->role;
201
                }
202
            }
203
204
            return $roles;
205
        });
206
207
    }
208
209
    /**
210
     * Check if the user has at least a role on a domain
211
     *
212
     * @param \Uccello\Core\Models\Domain $domain
213
     * @return boolean
214
     */
215
    public function hasRoleOnDomain($domain) : bool {
216
        if ($this->is_admin) {
217
            return true;
218
        }
219
220
        return $this->rolesOnDomain($domain)->count() > 0;
221
    }
222
223
    /**
224
     * Check if the user has at least a role on a domain or its descendants
225
     *
226
     * @param \Uccello\Core\Models\Domain $domain
227
     * @return boolean
228
     */
229
    public function hasRoleOnDescendantDomain(Domain $domain) : bool {
230
        if ($this->is_admin) {
231
            return true;
232
        }
233
234
        $hasRole = false;
235
236
        $descendants = Cache::remember('domain_'.$domain->slug.'_descendants', 600, function () use($domain) {
237
            return $domain->findDescendants()->get();
238
        });
239
240
        foreach ($descendants as $descendant) {
241
            if ($this->hasRoleOnDomain($descendant)) {
242
                $hasRole = true;
243
                break;
244
            }
245
        }
246
247
        return $hasRole;
248
    }
249
250
    /**
251
     * Returns all user capabilities on a module in a domain.
252
     * If the user has a capability in one of the parents of a domain, he also has it in that domain.
253
     *
254
     * @param \Uccello\Core\Models\Domain $domain
255
     * @param \Uccello\Core\Models\Module $module
256
     * @return \Illuminate\Support\Collection
257
     */
258
    public function capabilitiesOnModule(Domain $domain, Module $module) : Collection
259
    {
260
        $keyName = 'user_'.$this->id.'_'.$domain->slug.'_'.$module->name.'_capabilities';
261
262
        return Cache::remember($keyName, 600, function () use($domain, $module) {
263
            $capabilities = collect();
0 ignored issues
show
Bug introduced by
The function collect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

263
            $capabilities = /** @scrutinizer ignore-call */ collect();
Loading history...
264
265
            // Get the domain and all its parents
266
            $domainParents = $domain->findAncestors()->get();
267
268
            // Get user privileges on each domain
269
            foreach ($domainParents as $_domain) {
270
                $privileges = $this->privileges->where('domain_id', $_domain->id);
271
272
                foreach ($privileges as $privilege) {
273
274
                    foreach ($privilege->role->profiles as $profile) {
275
                        $capabilities = $capabilities->merge($profile->capabilitiesOnModule($module));
276
                    }
277
                }
278
            }
279
280
            return $capabilities;
281
        });
282
    }
283
284
    /**
285
     * Checks if the user has a capability on a module in a domain.
286
     *
287
     * @param string $capabilityName
288
     * @param \Uccello\Core\Models\Domain $domain
289
     * @param \Uccello\Core\Models\Module $module
290
     * @return boolean
291
     */
292
    public function hasCapabilityOnModule(string $capabilityName, Domain $domain, Module $module) : bool
293
    {
294
        $capability = capability($capabilityName);
295
296
        $userCapabilities = $this->capabilitiesOnModule($domain, $module);
297
298
        return $this->is_admin || $userCapabilities->contains($capability);
299
    }
300
301
    /**
302
     * Checks if the user can access to settings panel.
303
     * Checks if the user has at least one admin capability on admin modules in a domain.
304
     *
305
     * @param \Uccello\Core\Models\Domain|null $domain
306
     * @return boolean
307
     */
308
    public function canAccessToSettingsPanel(?Domain $domain) : bool
309
    {
310
        if (empty($domain)) {
311
            $domain = Domain::first();
312
        }
313
314
        $keyName = 'user_'.$this->id.'_'.$domain->slug.'_can_access_to_settings_panel';
315
316
        return Cache::remember($keyName, 600, function () use($domain) {
317
318
            $hasCapability = false;
319
320
            foreach (Module::all() as $module) {
321
                if ($module->isAdminModule() === true && $this->canAdmin($domain, $module)) {
322
                    $hasCapability = true;
323
                    break;
324
                }
325
            }
326
327
            return $hasCapability;
328
        });
329
    }
330
331
    /**
332
     * Checks if the user can admin a module in a domain.
333
     *
334
     * @param \Uccello\Core\Models\Domain $domain
335
     * @param \Uccello\Core\Models\Module $module
336
     * @return boolean
337
     */
338
    public function canAdmin(Domain $domain, Module $module) : bool
339
    {
340
        return $this->hasCapabilityOnModule('admin', $domain, $module);
341
    }
342
343
    /**
344
     * Checks if the user can create a module in a domain.
345
     *
346
     * @param \Uccello\Core\Models\Domain $domain
347
     * @param \Uccello\Core\Models\Module $module
348
     * @return boolean
349
     */
350
    public function canCreate(Domain $domain, Module $module) : bool
351
    {
352
        return $this->hasCapabilityOnModule('create', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
353
    }
354
355
    /**
356
     * Checks if the user can retrieve a module in a domain.
357
     *
358
     * @param \Uccello\Core\Models\Domain $domain
359
     * @param \Uccello\Core\Models\Module $module
360
     * @return boolean
361
     */
362
    public function canRetrieve(Domain $domain, Module $module) : bool
363
    {
364
        return $this->hasCapabilityOnModule('retrieve', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
365
    }
366
367
    /**
368
     * Checks if the user can update a module in a domain.
369
     *
370
     * @param \Uccello\Core\Models\Domain $domain
371
     * @param \Uccello\Core\Models\Module $module
372
     * @return boolean
373
     */
374
    public function canUpdate(Domain $domain, Module $module) : bool
375
    {
376
        return $this->hasCapabilityOnModule('update', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
377
    }
378
379
    /**
380
     * Checks if the user can delete a module in a domain.
381
     *
382
     * @param \Uccello\Core\Models\Domain $domain
383
     * @param \Uccello\Core\Models\Module $module
384
     * @return boolean
385
     */
386
    public function canDelete(Domain $domain, Module $module) : bool
387
    {
388
        return $this->hasCapabilityOnModule('delete', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
389
    }
390
391
    /**
392
     * Checks if the user can create by API a module in a domain.
393
     *
394
     * @param \Uccello\Core\Models\Domain $domain
395
     * @param \Uccello\Core\Models\Module $module
396
     * @return boolean
397
     */
398
    public function canCreateByApi(Domain $domain, Module $module) : bool
399
    {
400
        return $this->hasCapabilityOnModule('api-create', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
401
    }
402
403
    /**
404
     * Checks if the user can retrieve by API a module in a domain.
405
     *
406
     * @param \Uccello\Core\Models\Domain $domain
407
     * @param \Uccello\Core\Models\Module $module
408
     * @return boolean
409
     */
410
    public function canRetrieveByApi(Domain $domain, Module $module) : bool
411
    {
412
        return $this->hasCapabilityOnModule('api-retrieve', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
413
    }
414
415
    /**
416
     * Checks if the user can update by API a module in a domain.
417
     *
418
     * @param \Uccello\Core\Models\Domain $domain
419
     * @param \Uccello\Core\Models\Module $module
420
     * @return boolean
421
     */
422
    public function canUpdateByApi(Domain $domain, Module $module) : bool
423
    {
424
        return $this->hasCapabilityOnModule('api-update', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
425
    }
426
427
    /**
428
     * Checks if the user can delete by API a module in a domain.
429
     *
430
     * @param \Uccello\Core\Models\Domain $domain
431
     * @param \Uccello\Core\Models\Module $module
432
     * @return boolean
433
     */
434
    public function canDeleteByApi(Domain $domain, Module $module) : bool
435
    {
436
        return $this->hasCapabilityOnModule('api-delete', $domain, $module) || ($module->isAdminModule() && $this->canAdmin($domain, $module));
437
    }
438
439
    /**
440
     * Checks if the user has almost a role allowing to view data transversally
441
     *
442
     * @param \Uccello\Core\Models\Domain $domain
443
     * @return boolean
444
     */
445
    public function canSeeDescendantsRecords(Domain $domain) : bool
446
    {
447
        $allowed = false;
448
449
        if ($this->is_admin) {
450
            $allowed = true;
451
        } else {
452
            $roles = $this->rolesOnDomain($domain);
453
            foreach ($roles as $role) {
454
                if ($role->see_descendants_records) {
455
                    $allowed = true;
456
                    break;
457
                }
458
            }
459
        }
460
461
        return $allowed;
462
    }
463
464
    public function getAllowedGroupUids()
465
    {
466
        // Use cache
467
        $allowedGroups = Cache::rememberForever(
468
            'allowed_groups_for_' . ($this->is_admin ? 'admin' : $this->getKey()), 
469
            function () {
470
                return $this->getAllowedGroupUidsProcess();
471
            }
472
        );
473
474
        return $allowedGroups;
475
    }
476
477
    public function getAllowedGroupsAndUsers($addUsers = true)
478
    {
479
        // Use cache
480
        $allowedGroupsAndUsers = Cache::rememberForever(
481
            'allowed_group_users_for_' . ($addUsers ? 'u_' : '') . ($this->is_admin ? 'admin' : $this->getKey()), 
482
            function () use ($addUsers) {
483
                return $this->getAllowedGroupsAndUsersProcess($addUsers);
484
            }
485
        );
486
487
        return $allowedGroupsAndUsers;
488
    }
489
490
    protected function getAllowedGroupUidsProcess()
491
    {
492
        $allowedUserUids = collect();
0 ignored issues
show
Bug introduced by
The function collect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

492
        $allowedUserUids = /** @scrutinizer ignore-call */ collect();
Loading history...
493
494
        if ($this->is_admin) {
495
            $groups = Group::orderBy('name')->get();
496
        } else {
497
            $allowedUserUids[] = $this->uid;
498
499
            $groups = [];
500
            $users = [];
501
502
            foreach ($this->groups as $group) {
503
                $groups[$group->uid] = $group;
504
            };
505
506
            $this->addRecursiveChildrenGroups($groups, $users, $groups, false);
507
508
            $groups = collect($groups);
509
        }
510
511
        foreach ($groups as $uid => $group) {
512
            $allowedUserUids[] = $uid;
513
        }
514
515
        return $allowedUserUids;
516
    }
517
518
    protected function addRecursiveChildrenGroups(&$groups, &$users, $searchGroups, $addUsers = false)
519
    {
520
        foreach ($searchGroups as $uid => $searchGroup) {
521
            $searchChildrenGroups = [];
522
523
            foreach ($searchGroup->childrenGroups as $childrenGroup) {
524
                if (empty($groups[$childrenGroup->uid])) {
525
                    $groups[$childrenGroup->uid] = $childrenGroup;
526
                    $searchChildrenGroups[$childrenGroup->uid] = $childrenGroup;
527
                }
528
529
                if($addUsers)
530
                {
531
                    foreach ($childrenGroup->users as $user) {
532
                        if (empty($users[$user->uid])) {
533
                            $users[$user->uid] = $user;
534
                        }
535
                    }
536
                }
537
            }
538
539
            $this->addRecursiveChildrenGroups($groups, $users, $searchChildrenGroups, $addUsers);
540
        }
541
    }
542
543
    protected function getAllowedGroupsAndUsersProcess($addUsers = true)
544
    {
545
        $allowedUserUids = collect();
0 ignored issues
show
Bug introduced by
The function collect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

545
        $allowedUserUids = /** @scrutinizer ignore-call */ collect();
Loading history...
546
547
        if ($this->is_admin) {
548
            $groups = Group::orderBy('name')->get();
549
            $users  = \App\User::orderBy('name')->get();
0 ignored issues
show
Bug introduced by
The type App\User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
550
        } else {
551
            $allowedUserUids[] = [
552
                'uid' => $this->uid,
553
                'recordLabel' => $this->recordLabel
554
            ];
555
556
            $groups = [];
557
            $users = [];
558
559
            foreach ($this->groups as $group) {
560
                $groups[$group->uid] = $group;
561
562
                if($addUsers)
563
                {
564
                    foreach ($group->users as $user) {
565
                        if (empty($users[$user->uid])) {
566
                            $users[$user->uid] = $user;
567
                        }
568
                    }
569
                }
570
            };
571
572
            $this->addRecursiveChildrenGroups($groups, $users, $groups, $addUsers);
573
574
            $groups = collect($groups)->sortBy('name');
575
            $users  = collect($users)->sortBy('name');
576
        }
577
578
        foreach ($groups as $uid => $group) {
579
            $allowedUserUids[] = [
580
                'uid' => $group->uid,
581
                'recordLabel' => $group->recordLabel
582
            ];
583
        }
584
585
        foreach ($users as $uid => $user) {
586
            if($user->getKey() != $this->getKey()) {
587
                $allowedUserUids[] = [
588
                    'uid' => $user->uid,
589
                    'recordLabel' => $user->recordLabel
590
                ];
591
            }
592
        }
593
594
        return $allowedUserUids;
595
    }
596
}
597