Completed
Push — master ( 8c401a...033d43 )
by vistart
19:14
created

Organization::getSettings()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
ccs 6
cts 7
cp 0.8571
cc 4
eloc 7
nc 3
nop 1
crap 4.0466
1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\organization;
14
15
use rhosocial\base\models\traits\SelfBlameableTrait;
16
use rhosocial\base\models\queries\BaseBlameableQuery;
17
use rhosocial\base\models\queries\BaseUserQuery;
18
use rhosocial\organization\exceptions\DisallowMemberJoinOtherException;
19
use rhosocial\organization\exceptions\ExcludeOtherMembersException;
20
use rhosocial\organization\exceptions\OnlyAcceptCurrentOrgMemberException;
21
use rhosocial\organization\exceptions\OnlyAcceptSuperiorOrgMemberException;
22
use rhosocial\organization\rbac\roles\DepartmentAdmin;
23
use rhosocial\organization\rbac\roles\DepartmentCreator;
24
use rhosocial\organization\rbac\roles\OrganizationAdmin;
25
use rhosocial\organization\rbac\roles\OrganizationCreator;
26
use rhosocial\organization\queries\MemberQuery;
27
use rhosocial\organization\queries\OrganizationQuery;
28
use rhosocial\user\User;
29
use Yii;
30
use yii\base\Event;
31
use yii\base\InvalidParamException;
32
use yii\db\Exception;
33
use yii\db\IntegrityException;
34
35
/**
36
 * Organization.
37
 * This class is used to describe an organization or department, depending on the type property.
38
 * Organization or department should be created by the user, it is best not to directly implement their own such.
39
 *
40
 * In general, the organization needs to have `setUpOrganization` permission, and the user does not have this permission
41
 * by default. You need to give this permission to the user who created the organization in advance.
42
 * Department, affiliated with the organization or other department, also need the appropriate permission to set up.
43
 *
44
 * While this can work independently, we still strongly recommend that you declare the Organization class yourself and
45
 * inherit this.
46
 * Then you need to specify the Profile and Member class yourself, like following:
47
```php
48
class Organization extends \rhosocial\organization\Organization
49
{
50
    public $profileClass = Profile::class;
51
    public $memberClass = Member::class;
52
}
53
```
54
 * If you need to limit the number of subordinates, the number of members, you need to specify the appropriate class
55
 * name.
56
 * If there is no limit, you need to set it to false manually.
57
 *
58
 * @method Member createMember(array $config) Create member who is subordinate to this.
59
 * @property int $type Whether indicate this instance is an organization or a department.
60
 *
61
 * @property bool $isExcludeOtherMembers Determine whether the other organization and its subordinate departments
62
 * members could join in the current organization and its subordinate departments. (Only fit for Organization)
63
 * @property bool $isDisallowMemberJoinOther Determine whether the current organization and its subordinate
64
 * departments members could join in the other organization and its subordinate departments. (Only fit for Organization)
65
 * @property bool $isOnlyAcceptCurrentOrgMember Determine whether the current department only accept the member of
66
 * the top level organization. (Only fit for Department)
67
 * @property bool $isOnlyAcceptSuperiorOrgMember Determine whether the current department only accept the member of
68
 * the superior organization or department. (Only fit for Department)
69
 *
70
 * @property-read Member[] $members Get all member models of this organization/department.
71
 * @property-read User[] $memberUsers Get all members of this organization/department.
72
 * @property-read User $creator Get creator of this organization/department.
73
 * @property-read User[] $administrators Get administrators of this organization/department.
74
 * @property-read SubordinateLimit subordinateLimit
75
 * @property-read MemberLimit memberLimit
76
 * @property-read static|null $topOrganization The top level organization of current organization or departments.
77
 * @property-read Profile $profile Get profile model. Friendly to IDE.
78
 * @property-read OrganizationSetting[] $settings Get all settings.
79
 *
80
 * @version 1.0
81
 * @author vistart <[email protected]>
82
 */
83
class Organization extends User
84
{
85
    use SelfBlameableTrait;
86
87
    const TYPE_ORGANIZATION = 1;
88
    const TYPE_DEPARTMENT = 2;
89
90
    /**
91
     * @var boolean Organization does not need password and corresponding features.
92
     */
93
    public $passwordHashAttribute = false;
94
95
    /**
96
     * @var boolean Organization does not need password and corresponding features.
97
     */
98
    public $passwordResetTokenAttribute = false;
99
100
    /**
101
     * @var boolean Organization does not need password and corresponding features.
102
     */
103
    public $passwordHistoryClass = false;
104
105
    /**
106
     * @var boolean Organization does not need source.
107
     */
108
    public $sourceAttribute = false;
109
110
    /**
111
     * @var boolean Organization does not need auth key.
112
     */
113
    public $authKeyAttribute = false;
114
115
    /**
116
     * @var boolean Organization does not need access token.
117
     */
118
    public $accessTokenAttribute = false;
119
120
    /**
121
     * @var boolean Organization does not need login log.
122
     */
123
    public $loginLogClass = false;
124
125
    /**
126
     * @var string The Organization Profile Class
127
     */
128
    public $profileClass = Profile::class;
129
130
    /**
131
     * @var string The Member Class.
132
     */
133
    public $memberClass = Member::class;
134
135
    /**
136
     * @var string The Subordinate Limit Class
137
     */
138
    public $subordinateLimitClass = SubordinateLimit::class;
139
140
    /**
141
     * @var string The Member Limit Class
142
     */
143
    public $memberLimitClass = MemberLimit::class;
144
145
    /**
146
     * @var string The Organization Search Class
147
     */
148
    public $searchClass = OrganizationSearch::class;
149
150
    /**
151
     * @var string The Organization Setting Class
152
     */
153
    public $organizationSettingClass = OrganizationSetting::class;
154
155
    /**
156
     * @var Member
157
     */
158
    private $noInitMember;
159
160
    /**
161
     * @var SubordinateLimit
162
     */
163
    private $noInitSubordinateLimit;
164
165
    /**
166
     * @var MemberLimit
167
     */
168
    private $noInitMemberLimit;
169
170
    /**
171
     * @var OrganizationSetting
172
     */
173
    private $noInitOrganizationSetting;
174
175
    /**
176
     * @var User the creator of current Organization or Department.
177
     * This property is only available after registration.
178
     * Please do not access it at other times.
179
     * If you want to get creator model except registration, please
180
     * access [[$creator]] magic-property instead.
181
     */
182
    public $creatorModel;
183
184
    /**
185
     * @var array The configuration array of Organization Profile.
186
     * This property is only available after registration.
187
     * Please do not access it at other times.
188
     * If you want to get profile model except registration, please
189
     * access [[$profile]] magic-property instead.
190
     */
191
    public $profileConfig;
192
193
    const EVENT_BEFORE_ADD_MEMBER = 'eventBeforeAddMember';
194
    const EVENT_AFTER_ADD_MEMBER = 'eventAfterAddMember';
195
    const EVENT_BEFORE_REMOVE_MEMBER = 'eventBeforeRemoveMember';
196
    const EVENT_AFTER_REMOVE_MEMBER = 'eventAfterRemoveMember';
197
198
    public $cacheTagPrefix = 'tag_organization_';
199
200
    /**
201
     * @return Member
202
     */
203 51
    public function getNoInitMember()
204
    {
205 51
        if (!$this->noInitMember) {
206 51
            $class = $this->memberClass;
207 51
            $this->noInitMember = $class::buildNoInitModel();
208
        }
209 51
        return $this->noInitMember;
210
    }
211
212
    /**
213
     * @return SubordinateLimit
214
     */
215 2
    public function getNoInitSubordinateLimit()
216
    {
217 2
        if (!$this->noInitSubordinateLimit) {
218 2
            $class = $this->subordinateLimitClass;
219 2
            $this->noInitSubordinateLimit = $class::buildNoInitModel();
220
        }
221 2
        return $this->noInitSubordinateLimit;
222
    }
223
224
    /**
225
     * @return MemberLimit
226
     */
227 1
    public function getNoInitMemberLimit()
228
    {
229 1
        if (!$this->noInitMemberLimit) {
230 1
            $class = $this->memberLimitClass;
231 1
            $this->noInitMemberLimit = $class::buildNoInitModel();
232
        }
233 1
        return $this->noInitMemberLimit;
234
    }
235
236
    /**
237
     * @return null|OrganizationSetting
238
     */
239 31
    public function getNoInitOrganizationSetting()
240
    {
241 31
        if (!$this->noInitOrganizationSetting) {
242 31
            $class = $this->organizationSettingClass;
243 31
            if (empty($class)) {
244
                return null;
245
            }
246 31
            $this->noInitOrganizationSetting = $class::buildNoInitModel();
247
        }
248 31
        return $this->noInitOrganizationSetting;
249
    }
250
251
    /**
252
     * @return null|OrganizationSearch
253
     */
254
    public function getSearchModel()
255
    {
256
        $class = $this->searchClass;
257
        if (empty($class) || !class_exists($class)) {
258
            return null;
259
        }
260
        return new $class;
261
    }
262
263
    /**
264
     * @inheritdoc
265
     */
266 52
    public function init()
267
    {
268 52
        $this->parentAttribute = 'parent_guid';
269 52
        if (class_exists($this->memberClass)) {
270 52
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
271
        }
272 52
        if ($this->skipInit) {
273 52
            return;
274
        }
275 52
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
276 52
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
277 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeCreator']);
278 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeAdministrators']);
279 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokePermissions']);
280 52
        $this->initSelfBlameableEvents();
281 52
        parent::init();
282 52
    }
283
284
    /**
285
     * @inheritdoc
286
     */
287 1
    public function attributeLabels()
288
    {
289
        return [
290 1
            $this->guidAttribute => Yii::t('user', 'GUID'),
291 1
            $this->idAttribute => Yii::t('user', 'ID'),
292 1
            $this->ipAttribute => Yii::t('user', 'IP Address'),
293 1
            $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'),
294 1
            $this->parentAttribute => Yii::t('organization', 'Parent'),
295 1
            $this->createdAtAttribute => Yii::t('user', 'Creation Time'),
296 1
            $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'),
297 1
            $this->statusAttribute => Yii::t('user', 'Status'),
298 1
            'type' => Yii::t('user', 'Type'),
299 1
            'isExcludeOtherMembers' => Yii::t('organization', 'Exclude Other Members'),
300 1
            'isDisallowMemberJoinOther' => Yii::t('organization', 'Disallow Member to Join in Other Organizations'),
301 1
            'isOnlyAcceptCurrentOrgMember' => Yii::t('organization', 'Only Accept Current Organization Members'),
302 1
            'isOnlyAcceptSuperiorOrgMember' => Yii::t('organization', 'Only Accept Superior Organization Members'),
303
        ];
304
    }
305
306
    /**
307
     * @inheritdoc
308
     */
309 52
    public static function tableName()
310
    {
311 52
        return '{{%organization}}';
312
    }
313
314
    /**
315
     * Find.
316
     * Friendly to IDE.
317
     * @return OrganizationQuery
318
     */
319 52
    public static function find()
320
    {
321 52
        return parent::find();
322
    }
323
324 51
    protected function getTypeRules()
325
    {
326
        return [
327 51
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
328
            ['type', 'required'],
329 51
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
330
        ];
331
    }
332
333 51
    public function rules()
334
    {
335 51
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
336
    }
337
338
    /**
339
     * Get Member Query.
340
     * @return MemberQuery
341
     */
342 50
    public function getMembers()
343
    {
344 50
        return $this->hasMany($this->memberClass, [
345 50
            $this->getNoInitMember()->createdByAttribute => $this->guidAttribute
346 50
        ])->inverseOf('organization');
347
    }
348
349
    /**
350
     * Get organization member users' query.
351
     * @return BaseUserQuery
352
     */
353 6
    public function getMemberUsers()
354
    {
355 6
        $noInit = $this->getNoInitMember();
356 6
        $class = $noInit->memberUserClass;
357 6
        $noInitUser = $class::buildNoInitModel();
358 6
        return $this->hasMany($class, [
359 6
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
360 6
        ])->via('members')->inverseOf('atOrganizations');
361
    }
362
363
    /**
364
     * Get subordinate limit query.
365
     * @return null|BaseBlameableQuery
366
     */
367 2
    public function getSubordinateLimit()
368
    {
369 2
        if (empty($this->subordinateLimitClass)) {
370
            return null;
371
        }
372 2
        return $this->hasOne($this->subordinateLimitClass, [
373 2
            $this->getNoInitSubordinateLimit()->createdByAttribute => $this->guidAttribute
374
        ]);
375
    }
376
377
    /**
378
     * Get member limit query.
379
     * @return null|BaseBlameableQuery
380
     */
381 1
    public function getMemberLimit()
382
    {
383 1
        if (empty($this->memberLimitClass)) {
384
            return null;
385
        }
386 1
        return $this->hasOne($this->memberLimitClass, [
387 1
            $this->getNoInitMemberLimit()->createdByAttribute => $this->guidAttribute
388
        ]);
389
    }
390
391
    /**
392
     * @param string|null $item If you want to get all settings, please set it null.
393
     * @return null
394
     */
395 31
    public function getSettings($item = null)
396
    {
397 31
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
398
            return null;
399
        }
400 31
        $query = $this->hasMany($this->organizationSettingClass, [$this->getNoInitOrganizationSetting()->createdByAttribute => $this->guidAttribute]);
401 31
        if (!empty($item)) {
402 31
            $query = $query->andWhere([$this->getNoInitOrganizationSetting()->idAttribute => $item]);
403
        }
404 31
        return $query;
405
    }
406
407
    /**
408
     * @param $item
409
     * @param $value
410
     * @return bool|null Null if organization setting not enabled.
411
     */
412 31
    public function setSetting($item, $value)
413
    {
414 31
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
415
            return null;
416
        }
417 31
        $setting = $this->getSettings($item)->one();
418
        /* @var $setting OrganizationSetting */
419 31
        if (!$setting) {
420 31
            $setting = $this->create($this->organizationSettingClass, [
421 31
                $this->getNoInitOrganizationSetting()->idAttribute => $item,
422
            ]);
423
        }
424 31
        $setting->value = $value;
425 31
        return $setting->save();
426
    }
427
428
    /**
429
     * Get member with specified user.
430
     * @param User|string|integer $user
431
     * @return Member Null if `user` is not in this organization.
432
     */
433 50
    public function getMember($user)
434
    {
435 50
        return $this->getMembers()->user($user)->one();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getMembers()->user($user)->one(); of type yii\db\ActiveRecord|array|null adds the type array to the return on line 435 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
436
    }
437
438
    /**
439
     * Add member to organization.
440
     * @param Member|User|string|integer $member Member or User model, or User ID or GUID.
441
     * If member is created, it will be re-assigned to this parameter.
442
     * @see createMemberModel
443
     * @see createMemberModelWithUser
444
     * @return boolean
445
     * @throws DisallowMemberJoinOtherException
446
     * @throws ExcludeOtherMembersException
447
     * @throws OnlyAcceptCurrentOrgMemberException
448
     * @throws OnlyAcceptSuperiorOrgMemberException
449
     */
450 50
    public function addMember(&$member)
451
    {
452 50
        if ($this->getIsNewRecord()) {
453
            return false;
454
        }
455 50
        if ($this->hasReachedMemberLimit()) {
456 1
            return false;
457
        }
458 50
        $user = null;
459 50
        if ($member instanceof Member) {
460
            if ($member->getIsNewRecord()) {
461
                return false;
462
            }
463
            $user = $member->memberUser;
464
        }
465 50
        if ($member instanceof User) {
466 50
            $user = $member;
467
        }
468 50
        if (is_string($member) || is_int($member)) {
469
            $class = Yii::$app->user->identityClass;
470
            $user = $class::find()->guidOrId($member)->one();
471
        }
472 50
        if ($this->hasMember($user)) {
473
            return false;
474
        }
475 50
        $orgs = $user->getAtOrganizations()->all();
476
        /* @var $orgs Organization[] */
477 50
        foreach ($orgs as $org) {
478 31
            if ($org->topOrganization->isDisallowMemberJoinOther && !$org->topOrganization->equals($this->topOrganization)) {
0 ignored issues
show
Bug introduced by
It seems like $this->topOrganization can be null; however, equals() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
479 1
                throw new DisallowMemberJoinOtherException(Yii::t('organization', "An organization in which the user is located does not allow its members to join other organizations."));
480
            }
481 31
            if ($this->topOrganization->isExcludeOtherMembers && !$org->topOrganization->equals($this->topOrganization)) {
482 31
                throw new ExcludeOtherMembersException(Yii::t('organization', "The organization does not allow users who have joined other organizations to join."));
483
            }
484
        }
485 50
        if ($this->isDepartment() && $this->isOnlyAcceptCurrentOrgMember && !$this->topOrganization->hasMember($user)) {
486 1
            throw new OnlyAcceptCurrentOrgMemberException(Yii::t('organization' ,'This department is only accepted by members of the organization.'));
487
        }
488 50
        if ($this->isDepartment() && $this->isOnlyAcceptSuperiorOrgMember && !$this->parent->hasMember($user)) {
0 ignored issues
show
Bug introduced by
It seems like hasMember() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
489 1
            throw new OnlyAcceptSuperiorOrgMemberException(Yii::t('organization', 'This department only accepts members of the parent organization or department.'));
490
        }
491
492 50
        $this->trigger(self::EVENT_BEFORE_ADD_MEMBER);
493 50
        $model = null;
494 50
        if ($member instanceof Member) {
495
            $model = $this->createMemberModel($member);
496 50
        } elseif (($member instanceof User) || is_string($member) || is_int($member)) {
497 50
            $model = $this->createMemberModelWithUser($member);
498
        }
499 50
        $member = $model;
500 50
        $result = ($member instanceof Member) ? $member->save() : false;
501 50
        $this->trigger(self::EVENT_AFTER_ADD_MEMBER);
502 50
        return $result;
503
    }
504
505
    /**
506
     * Create member model, and set organization with this.
507
     * @param Member $member If this parameter is not new record, it's organization
508
     * will be set with this, and return it. Otherwise, it will extract `User`
509
     * model and create new `Member` model.
510
     * @see createMemberModelWithUser
511
     * @return Member
512
     */
513
    public function createMemberModel($member)
514
    {
515
        if (!$member->getIsNewRecord()) {
516
            $member->setOrganization($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<rhosocial\organization\Organization>, but the function expects a object<rhosocial\organization\BaseOrganization>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
517
            return $member;
518
        }
519
        return $this->createMemberModelWithUser($member->memberUser);
520
    }
521
522
    /**
523
     * Create member model with user, and set organization with this.
524
     * @param User|string|integer $user
525
     * @return Member
526
     */
527 50
    public function createMemberModelWithUser($user)
528
    {
529
        $config = [
530 50
            'memberUser' => $user,
531 50
            'organization' => $this,
532 50
            'nickname' => '',
533
        ];
534 50
        $member = $this->createMember($config);
535 50
        $member->nickname = $member->memberUser->profile->nickname;
536 50
        return $member;
537
    }
538
539
    /**
540
     * Remove member.
541
     * Note: the creator cannot be removed.
542
     * @param Member|User $member
543
     * @return boolean
544
     */
545 4
    public function removeMember(&$member)
546
    {
547 4
        if ($this->getIsNewRecord()) {
548
            return false;
549
        }
550 4
        $this->trigger(self::EVENT_BEFORE_REMOVE_MEMBER);
551 4
        if ($member instanceof $this->memberClass) {
552 4
            $member = $member->{$member->memberAttribute};
553
        }
554 4
        $member = $this->getMember($member);
555 4
        if (!$member || $member->isCreator()) {
556
            return false;
557
        }
558 4
        $result = $member->delete() > 0;
559 4
        $this->trigger(self::EVENT_AFTER_REMOVE_MEMBER);
560 4
        return $result;
561
    }
562
563
    /**
564
     * Remove administrator.
565
     * @param Member|User|integer|string $member Member instance, or User instance or its GUID or ID.
566
     * @param boolean $keep Keep member after administrator being revoked.
567
     * @return boolean
568
     * @throws IntegrityException
569
     */
570
    public function removeAdministrator(&$member, $keep = true)
571
    {
572
        if ($this->getIsNewRecord()) {
573
            return false;
574
        }
575
        if ($member instanceof $this->memberClass) {
576
            $member = $member->{$member->memberAttribute};
577
        }
578
        $member = $this->getMember($member);
579
        if ($member && $member->isAdministrator()) {
580
            if ($keep) {
581
                return $member->revokeAdministrator();
582
            }
583
            return $this->removeMember($member);
584
        }
585
        return false;
586
    }
587
588
    /**
589
     * 
590
     * @param Event $event
591
     * @throws IntegrityException
592
     * @return boolean
593
     */
594 51
    public function onAddProfile($event)
595
    {
596 51
        $profile = $event->sender->createProfile($event->data);
597 51
        if (!$profile->save()) {
598
            throw new IntegrityException('Profile Save Failed.');
599
        }
600 51
        return true;
601
    }
602
603
    /**
604
     * 
605
     * @param Event $event
606
     */
607 51
    public function onAssignCreator($event)
608
    {
609 51
        return $event->sender->addCreator($event->data);
610
    }
611
612
    /**
613
     * 
614
     * @param Event $event
615
     * @return boolean
616
     */
617 20
    public function onRevokeCreator($event)
618
    {
619 20
        $sender = $event->sender;
620
        /* @var $sender static */
621 20
        $member = $sender->getMemberCreators()->one();
622
        /* @var $member Member */
623 20
        $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
624 20
        return $member->revokeRole($role);
0 ignored issues
show
Documentation introduced by
$role is of type string, but the function expects a object<rhosocial\user\rbac\Role>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
625
    }
626
627
    /**
628
     * 
629
     * @param Event $event
630
     * @return boolean
631
     */
632 20
    public function onRevokeAdministrators($event)
633
    {
634 20
        $sender = $event->sender;
635
        /* @var $sender static */
636 20
        $members = $sender->getMemberAdministrators()->all();
637
        /* @var $members Member[] */
638 20
        foreach ($members as $member)
639
        {
640 1
            $member->revokeAdministrator();
641
        }
642 20
        return true;
643
    }
644
645
    /**
646
     * 
647
     * @param Event $event
648
     */
649 20
    public function onRevokePermissions($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
650
    {
651
        
652 20
    }
653
654
    /**
655
     * Check whether current instance is an organization.
656
     * @return boolean
657
     */
658 50
    public function isOrganization()
659
    {
660 50
        return $this->type == static::TYPE_ORGANIZATION;
661
    }
662
663
    /**
664
     * Check whether current instance if a department.
665
     * @return boolean
666
     */
667 50
    public function isDepartment()
668
    {
669 50
        return $this->type == static::TYPE_DEPARTMENT;
670
    }
671
672
    /**
673
     * Check whether the current organization has a member.
674
     * @param User|string|integer $user User instance, GUID or ID.
675
     * @return boolean
676
     */
677 50
    public function hasMember($user)
678
    {
679 50
        return !empty($this->getMember($user));
680
    }
681
682
    /**
683
     * Get member query which role is specified `Creator`.
684
     * @return MemberQuery
685
     */
686 24
    public function getMemberCreators()
687
    {
688 24
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
689
    }
690
691
    /**
692
     * Get member query which role is specified `Administrator`.
693
     * @return MemberQuery
694
     */
695 22
    public function getMemberAdministrators()
696
    {
697 22
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
698
    }
699
700
    /**
701
     * Get user query which role is specified `Creator`.
702
     * @return BaseUserQuery
703
     */
704 4
    public function getCreator()
705
    {
706 4
        $noInit = $this->getNoInitMember();
707 4
        $class = $noInit->memberUserClass;
708 4
        $noInitUser = $class::buildNoInitModel();
709 4
        return $this->hasOne($class, [
710 4
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
711 4
        ])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
712
    }
713
714
    /**
715
     * Get user query which role is specified `Administrator`.
716
     * @return BaseUserQuery
717
     */
718 2
    public function getAdministrators()
719
    {
720 2
        $noInit = $this->getNoInitMember();
721 2
        $class = $noInit->memberUserClass;
722 2
        $noInitUser = $class::buildNoInitModel();
723 2
        return $this->hasMany($class, [
724 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
725 2
        ])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
726
    }
727
728
    /**
729
     * 
730
     * @param User $user
731
     * @return boolean
732
     * @throws \Exception
733
     * @throws IntegrityException
734
     */
735 51
    protected function addCreator($user)
736
    {
737 51
        if (!$user) {
738 1
            throw new InvalidParamException('Creator Invalid.');
739
        }
740 50
        $member = $user;
741 50
        $transaction = Yii::$app->db->beginTransaction();
742
        try {
743 50
            if (!$this->addMember($member)) {
744
                throw new IntegrityException('Failed to add member.');
745
            }
746 50
            $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
747 50
            $member->assignRole($role);
0 ignored issues
show
Documentation introduced by
$role is of type string, but the function expects a object<rhosocial\user\rbac\Role>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The method assignRole does only exist in rhosocial\organization\Member, but not in rhosocial\user\User.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
748 50
            if (!$member->save()) {
749
                throw new IntegrityException('Failed to assign creator.');
750
            }
751 50
            $transaction->commit();
752
        } catch (\Exception $ex) {
753
            $transaction->rollBack();
754
            Yii::error($ex->getMessage(), __METHOD__);
755
            throw $ex;
756
        }
757 50
        return true;
758
    }
759
760
    /**
761
     * Add administrator.
762
     * @param User|integer|string $user User instance, or its GUID or ID.
763
     * @return boolean
764
     * @throws \Exception
765
     * @throws IntegrityException
766
     */
767 17
    public function addAdministrator($user)
768
    {
769 17
        $transaction = Yii::$app->db->beginTransaction();
770
        try {
771 17
            if (!$this->hasMember($user) && !$this->addMember($user)) {
772
                throw new IntegrityException(Yii::t('organization', 'Failed to add member.'));
773
            }
774 17
            $member = $this->getMember($user);
775 17
            $member->assignAdministrator();
776 17
            $transaction->commit();
777 2
        } catch (\Exception $ex) {
778 2
            $transaction->rollBack();
779 2
            Yii::error($ex->getMessage(), __METHOD__);
780 2
            throw $ex;
781
        }
782 17
        return true;
783
    }
784
785
    /**
786
     * Check whether the current organization has administrator.
787
     * @param User|integer|string $user
788
     * @return boolean
789
     */
790 2
    public function hasAdministrator($user)
791
    {
792 2
        $member = $this->getMember($user);
793 2
        if (!$member) {
794
            return false;
795
        }
796 2
        return $member->isAdministrator();
797
    }
798
799
    /**
800
     * Check whether this organization has reached the upper limit of subordinates.
801
     * @return boolean
802
     */
803 19
    public function hasReachedSubordinateLimit()
804
    {
805 19
        $remaining = $this->getRemainingSubordinatePlaces();
806 19
        if ($remaining === false) {
807
            return false;
808
        }
809 19
        return $remaining <= 0;
810
    }
811
812
    /**
813
     * Get the remaining places of subordinates.
814
     * @return bool|int False if no limit
815
     */
816 19
    public function getRemainingSubordinatePlaces()
817
    {
818 19
        $class = $this->subordinateLimitClass;
819 19
        if (empty($class)) {
820
            return false;
821
        }
822 19
        $limit = $class::getLimit($this);
823 19
        if ($limit === false) {
824
            return false;
825
        }
826 19
        $count = (int)$this->getChildren()->count();
827 19
        return $limit - $count;
828
    }
829
830
    /**
831
     * Check whether this organization has reached the upper limit of members.
832
     * @return boolean
833
     */
834 50
    public function hasReachedMemberLimit()
835
    {
836 50
        $remaining = $this->getRemainingMemberPlaces();
837 50
        if ($remaining === false) {
838
            return false;
839
        }
840 50
        return $remaining <= 0;
841
    }
842
843
    /**
844
     * Get the remaining places of members.
845
     * @return bool|int False if no limit.
846
     */
847 50
    public function getRemainingMemberPlaces()
848
    {
849 50
        $class = $this->memberLimitClass;
850 50
        if (empty($class)) {
851
            return false;
852
        }
853 50
        $limit = $class::getLimit($this);
854 50
        if ($limit === false) {
855
            return false;
856
        }
857 50
        $count = (int)$this->getMembers()->count();
858 50
        return $limit - $count;
859
    }
860
861
    const SETTING_ITEM_EXCLUDE_OTHER_MEMBERS = 'exclude_other_members';
862
863
    /**
864
     * @return bool
865
     */
866 31
    public function getIsExcludeOtherMembers()
867
    {
868 31
        $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
869 31
        if (!$setting) {
870 31
            $this->setSetting(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS, '0');
871 31
            $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
872
        }
873 31
        return $setting->value == '1';
874
    }
875
876
    /**
877
     * @param bool $value
878
     * @return bool
879
     */
880 2
    public function setIsExcludeOtherMembers($value = true)
881
    {
882 2
        return $this->setSetting(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS, $value ? '1' : '0');
883
    }
884
885
    const SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER = 'disallow_member_join_other';
886
887
    /**
888
     * @return bool
889
     */
890 31
    public function getIsDisallowMemberJoinOther()
891
    {
892 31
        $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
893 31
        if (!$setting) {
894 31
            $this->setSetting(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER, '0');
895 31
            $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
896
        }
897 31
        return $setting->value == '1';
898
    }
899
900
    /**
901
     * @param bool $value
902
     * @return bool
903
     */
904 2
    public function setIsDisallowMemberJoinOther($value = true)
905
    {
906 2
        return $this->setSetting(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER, $value ? '1' : '0');
907
    }
908
909
    const SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER = 'only_accept_current_org_member';
910
911
    /**
912
     * @return bool
913
     */
914 18
    public function getIsOnlyAcceptCurrentOrgMember()
915
    {
916 18
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
917 18
        if (!$setting) {
918 18
            $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER, '0');
919 18
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
920
        }
921 18
        return $setting->value == '1';
922
    }
923
924
    /**
925
     * @param bool $value
926
     * @return bool
927
     */
928 2
    public function setIsOnlyAcceptCurrentOrgMember($value = true)
929
    {
930 2
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER, $value ? '1' : '0');
931
    }
932
933
    const SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER = 'only_accept_superior_org_member';
934
935
    /**
936
     * @return bool
937
     */
938 18
    public function getIsOnlyAcceptSuperiorOrgMember()
939
    {
940 18
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
941 18
        if (!$setting) {
942 18
            $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER, '0');
943 18
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
944
        }
945 18
        return $setting->value == '1';
946
    }
947
948
    /**
949
     * @param bool $value
950
     * @return bool
951
     */
952 2
    public function setIsOnlyAcceptSuperiorOrgMember($value = true)
953
    {
954 2
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER, $value ? '1' : '0');
955
    }
956
957
    /**
958
     * @return $this|null|static
959
     */
960 31
    public function getTopOrganization()
961
    {
962 31
        if ($this->isOrganization()) {
963 31
            return $this;
964
        }
965 18
        $chain = $this->getAncestorChain();
966 18
        return static::findOne(end($chain));
967
    }
968
969
    /**
970
     * Check whether the subordinates have the [[$user]]
971
     * Note, this operation may consume the quantity of database selection.
972
     * @param User $user
973
     * @return bool
974
     */
975 2
    public function hasMemberInSubordinates($user)
976
    {
977 2
        if ($this->getChildren()->joinWith(['memberUsers mu_alias'])
978 2
            ->andWhere(['mu_alias.' . $user->guidAttribute => $user->getGUID()])->exists()) {
979 1
            return true;
980
        }
981 2
        $children = $this->children;
982
        /* @var $children static[] */
983 2
        foreach ($children as $child) {
984 2
            if ($child->hasMemberInSubordinates($user)) {
985 2
                return true;
986
            }
987
        }
988 2
        return false;
989
    }
990
}
991