Completed
Push — master ( 09ccfa...0669ac )
by vistart
06:36
created

Organization::addMember()   C

Complexity

Conditions 25
Paths 155

Size

Total Lines 54
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 31.316

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 54
ccs 29
cts 37
cp 0.7838
rs 5.9031
cc 25
eloc 37
nc 155
nop 1
crap 31.316

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\user\User;
19
use rhosocial\organization\rbac\roles\DepartmentAdmin;
20
use rhosocial\organization\rbac\roles\DepartmentCreator;
21
use rhosocial\organization\rbac\roles\OrganizationAdmin;
22
use rhosocial\organization\rbac\roles\OrganizationCreator;
23
use rhosocial\organization\queries\MemberQuery;
24
use rhosocial\organization\queries\OrganizationQuery;
25
use Yii;
26
use yii\base\Event;
27
use yii\base\InvalidParamException;
28
use yii\db\IntegrityException;
29
30
/**
31
 * Base Organization.
32
 * This class is an abstract class that can not be instantiated directly.
33
 * You can use [[Organization]] or [[Department]] instead.
34
 *
35
 * @method Member createMember(array $config) Create member who is subordinate to this.
36
 * @property int $type Whether indicate this instance is an organization or a department.
37
 * @property int $eom Fit for [[$isExcludeOtherMembers]]. Do not modify it directly.
38
 * @property int $djo Fit for [[$isDisallowMemberJoinInOther]]. Do not modify it directly.
39
 * @property int $oacm Fit for [[$isOnlyAcceptCurrentOrgMember]]. Do not modify it directly.
40
 * @property int $oasm Fit for [[$isOnlyAcceptSuperiorOrgMember]]. Do not modify it directly.
41
 *
42
 * @property bool $isExcludeOtherMembers Determine whether the other organization and its subordinate departments
43
 * members could join in the current organization and its subordinate departments. (Only fit for Organization)
44
 * @property bool $isDisallowMemberJoinInOther Determine whether the current organization and its subordinate
45
 * departments members could join in the other organization and its subordinate departments. (Only fit for Organization)
46
 * @property bool $isOnlyAcceptCurrentOrgMember Determine whether the current department only accept the member of
47
 * the top level organization. (Only fit for Department)
48
 * @property bool $isOnlyAcceptSuperiorOrgMember Determine whether the current department only accept the member of
49
 * the superior organization or department. (Only fit for Department)
50
 *
51
 * @property-read Member[] $members Get all member models of this organization/department.
52
 * @property-read User[] $memberUsers Get all members of this organization/department.
53
 * @property-read User $creator Get creator of this organization/department.
54
 * @property-read User[] $administrators Get administrators of this organization/department.
55
 * @property-read SubordinateLimit subordinateLimit
56
 * @property-read MemberLimit memberLimit
57
 * @property-read static|null $topOrganization The top level organization of current organization or departments.
58
 *
59
 * @version 1.0
60
 * @author vistart <[email protected]>
61
 */
62
class Organization extends User
63
{
64
    use SelfBlameableTrait;
65
66
    const TYPE_ORGANIZATION = 1;
67
    const TYPE_DEPARTMENT = 2;
68
69
    /**
70
     * @var boolean Organization does not need password and corresponding features.
71
     */
72
    public $passwordHashAttribute = false;
73
74
    /**
75
     * @var boolean Organization does not need password and corresponding features.
76
     */
77
    public $passwordResetTokenAttribute = false;
78
79
    /**
80
     * @var boolean Organization does not need password and corresponding features.
81
     */
82
    public $passwordHistoryClass = false;
83
84
    /**
85
     * @var boolean Organization does not need source.
86
     */
87
    public $sourceAttribute = false;
88
89
    /**
90
     * @var boolean Organization does not need auth key.
91
     */
92
    public $authKeyAttribute = false;
93
94
    /**
95
     * @var boolean Organization does not need access token.
96
     */
97
    public $accessTokenAttribute = false;
98
99
    /**
100
     *
101
     * @var boolean Organization does not need login log.
102
     */
103
    public $loginLogClass = false;
104
105
    public $profileClass = Profile::class;
106
107
    public $memberClass = Member::class;
108
    public $subordinateLimitClass = SubordinateLimit::class;
109
    public $memberLimitClass = MemberLimit::class;
110
    public $searchClass = OrganizationSearch::class;
111
    /**
112
     * @var Member
113
     */
114
    private $noInitMember;
115
    /**
116
     * @var SubordinateLimit
117
     */
118
    private $noInitSubordinateLimit;
119
    /**
120
     * @var MemberLimit
121
     */
122
    private $noInitMemberLimit;
123
    public $creatorModel;
124
    public $profileConfig;
125
126
    const EVENT_BEFORE_ADD_MEMBER = 'eventBeforeAddMember';
127
    const EVENT_AFTER_ADD_MEMBER = 'eventAfterAddMember';
128
    const EVENT_BEFORE_REMOVE_MEMBER = 'eventBeforeRemoveMember';
129
    const EVENT_AFTER_REMOVE_MEMBER = 'eventAfterRemoveMember';
130
131
    /**
132
     * @return Member
133
     */
134 49
    public function getNoInitMember()
135
    {
136 49
        if (!$this->noInitMember) {
137 49
            $class = $this->memberClass;
138 49
            $this->noInitMember = $class::buildNoInitModel();
139
        }
140 49
        return $this->noInitMember;
141
    }
142
143
    /**
144
     * @return SubordinateLimit
145
     */
146 2
    public function getNoInitSubordinateLimit()
147
    {
148 2
        if (!$this->noInitSubordinateLimit) {
149 2
            $class = $this->subordinateLimitClass;
150 2
            $this->noInitSubordinateLimit = $class::buildNoInitModel();
151
        }
152 2
        return $this->noInitSubordinateLimit;
153
    }
154
155
    /**
156
     * @return MemberLimit
157
     */
158 1
    public function getNoInitMemberLimit()
159
    {
160 1
        if (!$this->noInitMemberLimit) {
161 1
            $class = $this->memberLimitClass;
162 1
            $this->noInitMemberLimit = $class::buildNoInitModel();
163
        }
164 1
        return $this->noInitMemberLimit;
165
    }
166
167
    /**
168
     * @return null|OrganizationSearch
169
     */
170
    public function getSearchModel()
171
    {
172
        $class = $this->searchClass;
173
        if (empty($class) || !class_exists($class)) {
174
            return null;
175
        }
176
        return new $class;
177
    }
178
179 50
    public function init()
180
    {
181 50
        $this->parentAttribute = 'parent_guid';
182 50
        if (class_exists($this->memberClass)) {
183 50
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
184
        }
185 50
        if ($this->skipInit) {
186 50
            return;
187
        }
188 50
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
189 50
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
190 50
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeCreator']);
191 50
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeAdministrators']);
192 50
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokePermissions']);
193 50
        $this->initSelfBlameableEvents();
194 50
        parent::init();
195 50
    }
196
197
    /**
198
     * @inheritdoc
199
     */
200 1
    public function attributeLabels()
201
    {
202
        return [
203 1
            'guid' => Yii::t('user', 'GUID'),
204 1
            'id' => Yii::t('user', 'ID'),
205 1
            'ip' => Yii::t('user', 'IP Address'),
206 1
            'ip_type' => Yii::t('user', 'IP Address Type'),
207 1
            'parent' => Yii::t('organization', 'Parent'),
208 1
            'created_at' => Yii::t('user', 'Creation Time'),
209 1
            'updated_at' => Yii::t('user', 'Last Updated Time'),
210 1
            'status' => Yii::t('user', 'Status'),
211 1
            'type' => Yii::t('user', 'Type'),
212 1
            'isExcludeOtherMembers' => Yii::t('organization', 'Exclude Other Members'),
213 1
            'isDisallowMemberJoinOther' => Yii::t('organization', 'Disallow Member to Join in Other Organizations'),
214 1
            'isOnlyAcceptCurrentOrgMember' => Yii::t('organization', 'Only Accept Current Organization Members'),
215 1
            'isOnlyAcceptSuperiorOrgMember' => Yii::t('organization', 'Only Accept Superior Organization Members'),
216
        ];
217
    }
218
219
    /**
220
     * @inheritdoc
221
     */
222 50
    public static function tableName()
223
    {
224 50
        return '{{%organization}}';
225
    }
226
227
    /**
228
     * Find.
229
     * Friendly to IDE.
230
     * @return OrganizationQuery
231
     */
232 50
    public static function find()
233
    {
234 50
        return parent::find();
235
    }
236
237 49
    protected function getTypeRules()
238
    {
239
        return [
240 49
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
241
            ['type', 'required'],
242 49
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
243
            [['eom', 'djo', 'oacm', 'oasm'], 'default', 'value' => 0],
244
        ];
245
    }
246
247 49
    public function rules()
248
    {
249 49
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
250
    }
251
252
    /**
253
     * Get Member Query.
254
     * @return MemberQuery
255
     */
256 48
    public function getMembers()
257
    {
258 48
        return $this->hasMany($this->memberClass, [
259 48
            $this->getNoInitMember()->createdByAttribute => $this->guidAttribute
260 48
        ])->inverseOf('organization');
261
    }
262
263
    /**
264
     * Get organization member users' query.
265
     * @return BaseUserQuery
266
     */
267 6
    public function getMemberUsers()
268
    {
269 6
        $noInit = $this->getNoInitMember();
270 6
        $class = $noInit->memberUserClass;
271 6
        $noInitUser = $class::buildNoInitModel();
272 6
        return $this->hasMany($class, [
273 6
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
274 6
        ])->via('members')->inverseOf('atOrganizations');
275
    }
276
277
    /**
278
     * Get subordinate limit query.
279
     * @return null|BaseBlameableQuery
280
     */
281 2
    public function getSubordinateLimit()
282
    {
283 2
        if (empty($this->subordinateLimitClass)) {
284
            return null;
285
        }
286 2
        return $this->hasOne($this->subordinateLimitClass, [
287 2
            $this->getNoInitSubordinateLimit()->createdByAttribute => $this->guidAttribute
288
        ]);
289
    }
290
291
    /**
292
     * Get member limit query.
293
     * @return null|BaseBlameableQuery
294
     */
295 1
    public function getMemberLimit()
296
    {
297 1
        if (empty($this->memberLimitClass)) {
298
            return null;
299
        }
300 1
        return $this->hasOne($this->memberLimitClass, [
301 1
            $this->getNoInitMemberLimit()->createdByAttribute => $this->guidAttribute
302
        ]);
303
    }
304
305
    /**
306
     * Get member with specified user.
307
     * @param User|string|integer $user
308
     * @return Member Null if `user` is not in this organization.
309
     */
310 48
    public function getMember($user)
311
    {
312 48
        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 312 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
313
    }
314
315
    /**
316
     * Add member to organization.
317
     * @param Member|User|string|integer $member Member or User model, or User ID or GUID.
318
     * If member is created, it will be re-assigned to this parameter.
319
     * @see createMemberModel
320
     * @see createMemberModelWithUser
321
     * @return boolean
322
     */
323 48
    public function addMember(&$member)
324
    {
325 48
        if ($this->getIsNewRecord()) {
326
            return false;
327
        }
328 48
        if ($this->hasReachedMemberLimit()) {
329 1
            return false;
330
        }
331 48
        $user = null;
332 48
        if ($member instanceof Member) {
333 1
            if ($member->getIsNewRecord()) {
334 1
                return false;
335
            }
336
            $user = $member->memberUser;
337
        }
338 48
        if ($member instanceof User) {
339 48
            $user = $member;
340
        }
341 48
        if (is_string($member) || is_int($member)) {
342
            $class = Yii::$app->user->identityClass;
343
            $user = $class::find()->guidOrId($member);
344
        }
345 48
        if ($this->hasMember($user)) {
346
            return false;
347
        }
348 48
        $orgs = $user->getAtOrganizations()->all();
349
        /* @var $orgs Organization[] */
350 48
        foreach ($orgs as $org) {
351 29
            if ($org->topOrganization->isDisallowMemberJoinInOther && !$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...
352 1
                return false;
353
            }
354 29
            if ($this->topOrganization->isExcludeOtherMembers && !$org->topOrganization->equals($this->topOrganization)) {
355 29
                return false;
356
            }
357
        }
358 48
        if ($this->isDepartment() && $this->isOnlyAcceptCurrentOrgMember && !$this->topOrganization->hasMember($user)) {
359
            return false;
360
        }
361 48
        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...
362
            return false;
363
        }
364
365 48
        $this->trigger(self::EVENT_BEFORE_ADD_MEMBER);
366 48
        $model = null;
367 48
        if ($member instanceof Member) {
368
            $model = $this->createMemberModel($member);
369 48
        } elseif (($member instanceof User) || is_string($member) || is_int($member)) {
370 48
            $model = $this->createMemberModelWithUser($member);
371
        }
372 48
        $member = $model;
373 48
        $result = ($member instanceof Member) ? $member->save() : false;
374 48
        $this->trigger(self::EVENT_AFTER_ADD_MEMBER);
375 48
        return $result;
376
    }
377
378
    /**
379
     * Create member model, and set organization with this.
380
     * @param Member $member If this parameter is not new record, it's organization
381
     * will be set with this, and return it. Otherwise, it will extract `User`
382
     * model and create new `Member` model.
383
     * @see createMemberModelWithUser
384
     * @return Member
385
     */
386
    public function createMemberModel($member)
387
    {
388
        if (!$member->getIsNewRecord()) {
389
            $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...
390
            return $member;
391
        }
392
        return $this->createMemberModelWithUser($member->memberUser);
393
    }
394
395
    /**
396
     * Create member model with user, and set organization with this.
397
     * @param User|string|integer $user
398
     * @return Member
399
     */
400 48
    public function createMemberModelWithUser($user)
401
    {
402
        $config = [
403 48
            'memberUser' => $user,
404 48
            'organization' => $this,
405 48
            'nickname' => '',
406
        ];
407 48
        $member = $this->createMember($config);
408 48
        $member->nickname = $member->memberUser->profile->nickname;
409 48
        return $member;
410
    }
411
412
    /**
413
     * Remove member.
414
     * Note: the creator cannot be removed.
415
     * @param Member|User $member
416
     * @return boolean
417
     */
418 2
    public function removeMember(&$member)
419
    {
420 2
        if ($this->getIsNewRecord()) {
421
            return false;
422
        }
423 2
        $this->trigger(self::EVENT_BEFORE_REMOVE_MEMBER);
424 2
        if ($member instanceof $this->memberClass) {
425 2
            $member = $member->{$member->memberAttribute};
426
        }
427 2
        $member = $this->getMember($member);
428 2
        if (!$member || $member->isCreator()) {
429
            return false;
430
        }
431 2
        $result = $member->delete() > 0;
432 2
        $this->trigger(self::EVENT_AFTER_REMOVE_MEMBER);
433 2
        return $result;
434
    }
435
436
    /**
437
     * Remove administrator.
438
     * @param Member|User|integer|string $member Member instance, or User instance or its GUID or ID.
439
     * @param boolean $keep Keep member after administrator being revoked.
440
     * @return boolean
441
     * @throws IntegrityException
442
     */
443
    public function removeAdministrator(&$member, $keep = true)
444
    {
445
        if ($this->getIsNewRecord()) {
446
            return false;
447
        }
448
        if ($member instanceof $this->memberClass) {
449
            $member = $member->{$member->memberAttribute};
450
        }
451
        $member = $this->getMember($member);
452
        if ($member && $member->isAdministrator()) {
453
            if ($keep) {
454
                return $member->revokeAdministrator();
455
            }
456
            return $this->removeMember($member);
457
        }
458
        return false;
459
    }
460
461
    /**
462
     * 
463
     * @param Event $event
464
     * @throws IntegrityException
465
     * @return boolean
466
     */
467 49
    public function onAddProfile($event)
468
    {
469 49
        $profile = $event->sender->createProfile($event->data);
470 49
        if (!$profile->save()) {
471
            throw new IntegrityException('Profile Save Failed.');
472
        }
473 49
        return true;
474
    }
475
476
    /**
477
     * 
478
     * @param Event $event
479
     */
480 49
    public function onAssignCreator($event)
481
    {
482 49
        return $event->sender->addCreator($event->data);
483
    }
484
485
    /**
486
     * 
487
     * @param Event $event
488
     * @return boolean
489
     */
490 20
    public function onRevokeCreator($event)
491
    {
492 20
        $sender = $event->sender;
493
        /* @var $sender static */
494 20
        $member = $sender->getMemberCreators()->one();
495
        /* @var $member Member */
496 20
        $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
497 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...
498
    }
499
500
    /**
501
     * 
502
     * @param Event $event
503
     * @return boolean
504
     */
505 20
    public function onRevokeAdministrators($event)
506
    {
507 20
        $sender = $event->sender;
508
        /* @var $sender static */
509 20
        $members = $sender->getMemberAdministrators()->all();
510
        /* @var $members Member[] */
511 20
        foreach ($members as $member)
512
        {
513 1
            $member->revokeAdministrator();
514
        }
515 20
        return true;
516
    }
517
518
    /**
519
     * 
520
     * @param Event $event
521
     */
522 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...
523
    {
524
        
525 20
    }
526
527
    /**
528
     * Check whether current instance is an organization.
529
     * @return boolean
530
     */
531 48
    public function isOrganization()
532
    {
533 48
        return $this->type == static::TYPE_ORGANIZATION;
534
    }
535
536
    /**
537
     * Check whether current instance if a department.
538
     * @return boolean
539
     */
540 48
    public function isDepartment()
541
    {
542 48
        return $this->type == static::TYPE_DEPARTMENT;
543
    }
544
545
    /**
546
     * Check whether the current organization has a member.
547
     * @param User|string|integer $user User instance, GUID or ID.
548
     * @return boolean
549
     */
550 48
    public function hasMember($user)
551
    {
552 48
        return !is_null($this->getMember($user));
553
    }
554
555
    /**
556
     * Get member query which role is specified `Creator`.
557
     * @return MemberQuery
558
     */
559 24
    public function getMemberCreators()
560
    {
561 24
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
562
    }
563
564
    /**
565
     * Get member query which role is specified `Administrator`.
566
     * @return MemberQuery
567
     */
568 22
    public function getMemberAdministrators()
569
    {
570 22
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
571
    }
572
573
    /**
574
     * Get user query which role is specified `Creator`.
575
     * @return BaseUserQuery
576
     */
577 4
    public function getCreator()
578
    {
579 4
        $noInit = $this->getNoInitMember();
580 4
        $class = $noInit->memberUserClass;
581 4
        $noInitUser = $class::buildNoInitModel();
582 4
        return $this->hasOne($class, [
583 4
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
584 4
        ])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
585
    }
586
587
    /**
588
     * Get user query which role is specified `Administrator`.
589
     * @return BaseUserQuery
590
     */
591 2
    public function getAdministrators()
592
    {
593 2
        $noInit = $this->getNoInitMember();
594 2
        $class = $noInit->memberUserClass;
595 2
        $noInitUser = $class::buildNoInitModel();
596 2
        return $this->hasMany($class, [
597 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
598 2
        ])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
599
    }
600
601
    /**
602
     * 
603
     * @param User $user
604
     * @return boolean
605
     * @throws \Exception
606
     * @throws IntegrityException
607
     */
608 49
    protected function addCreator($user)
609
    {
610 49
        if (!$user) {
611 1
            throw new InvalidParamException('Creator Invalid.');
612
        }
613 48
        $member = $user;
614 48
        $transaction = Yii::$app->db->beginTransaction();
615
        try {
616 48
            if (!$this->addMember($member)) {
617
                throw new IntegrityException('Failed to add member.');
618
            }
619 48
            $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
620 48
            $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...
621 48
            if (!$member->save()) {
622
                throw new IntegrityException('Failed to assign creator.');
623
            }
624 48
            $transaction->commit();
625
        } catch (\Exception $ex) {
626
            $transaction->rollBack();
627
            Yii::error($ex->getMessage(), __METHOD__);
628
            throw $ex;
629
        }
630 48
        return true;
631
    }
632
633
    /**
634
     * Add administrator.
635
     * @param User|integer|string $user User instance, or its GUID or ID.
636
     * @return boolean
637
     * @throws \Exception
638
     * @throws IntegrityException
639
     */
640 15
    public function addAdministrator($user)
641
    {
642 15
        $transaction = Yii::$app->db->beginTransaction();
643
        try {
644 15
            if (!$this->hasMember($user) && !$this->addMember($user)) {
645 2
                throw new IntegrityException(Yii::t('organization', 'Failed to add member.'));
646
            }
647 15
            $member = $this->getMember($user);
648 15
            $member->assignAdministrator();
649 15
            $transaction->commit();
650 2
        } catch (\Exception $ex) {
651 2
            $transaction->rollBack();
652 2
            Yii::error($ex->getMessage(), __METHOD__);
653 2
            throw $ex;
654
        }
655 15
        return true;
656
    }
657
658
    /**
659
     * Check whether the current organization has administrator.
660
     * @param User|integer|string $user
661
     * @return boolean
662
     */
663 2
    public function hasAdministrator($user)
664
    {
665 2
        $member = $this->getMember($user);
666 2
        if (!$member) {
667
            return false;
668
        }
669 2
        return $member->isAdministrator();
670
    }
671
672
    /**
673
     * Check whether this organization has reached the upper limit of subordinates.
674
     * @return boolean
675
     */
676 16
    public function hasReachedSubordinateLimit()
677
    {
678 16
        $class = $this->subordinateLimitClass;
679 16
        if (empty($class)) {
680
            return false;
681
        }
682 16
        $limit = $class::getLimit($this);
683 16
        if ($limit === false) {
684
            return false;
685
        }
686 16
        $count = (int)$this->getChildren()->count();
687 16
        return $count >= $limit;
688
    }
689
690
    /**
691
     * Check whether this organization has reached the upper limit of members.
692
     * @return boolean
693
     */
694 48
    public function hasReachedMemberLimit()
695
    {
696 48
        $class = $this->memberLimitClass;
697 48
        if (empty($class)) {
698
            return false;
699
        }
700 48
        $limit = $class::getLimit($this);
701 48
        if ($limit === false) {
702
            return false;
703
        }
704 48
        $count = (int)$this->getMembers()->count();
705 48
        return $count >= $limit;
706
    }
707
708 29
    public function getIsExcludeOtherMembers()
709
    {
710 29
        return $this->eom > 0;
711
    }
712 2
    public function setIsExcludeOtherMembers($value = true)
713
    {
714 2
        $this->eom = ($value) ? 1 : 0;
715 2
    }
716 29
    public function getIsDisallowMemberJoinInOther()
717
    {
718 29
        return $this->djo > 0;
719
    }
720 2
    public function setIsDisallowMemberJoinInOther($value = true)
721
    {
722 2
        $this->djo = ($value) ? 1 : 0;
723 2
    }
724 15
    public function getIsOnlyAcceptCurrentOrgMember()
725
    {
726 15
        return $this->oacm > 0;
727
    }
728 1
    public function setIsOnlyAcceptCurrentOrgMember($value = true)
729
    {
730 1
        $this->oacm = ($value) ? 1 : 0;
731 1
    }
732 15
    public function getIsOnlyAcceptSuperiorOrgMember()
733
    {
734 15
        return $this->oasm > 0;
735
    }
736 1
    public function setIsOnlyAcceptSuperiorOrgMember($value = true)
737
    {
738 1
        $this->oasm = ($value) ? 1 : 0;
739 1
    }
740
741
    /**
742
     * @return $this|null|static
743
     */
744 29
    public function getTopOrganization()
745
    {
746 29
        if ($this->isOrganization()) {
747 29
            return $this;
748
        }
749 15
        $chain = $this->getAncestorChain();
750 15
        return static::findOne(end($chain));
751
    }
752
753
    /**
754
     * Check whether the subordinates have the [[$user]]
755
     * Note, this operation may consume the quantity of database selection.
756
     * @param User $user
757
     * @return bool
758
     */
759 2
    public function hasMemberInSubordinates($user)
760
    {
761 2
        if ($this->getChildren()->joinWith(['memberUsers mu_alias'])
762 2
            ->andWhere(['mu_alias.' . $user->guidAttribute => $user->getGUID()])->exists()) {
763 1
            return true;
764
        }
765 2
        $children = $this->children;
766
        /* @var $children static[] */
767 2
        foreach ($children as $child) {
768 2
            if ($child->hasMemberInSubordinates($user)) {
769 2
                return true;
770
            }
771
        }
772 2
        return false;
773
    }
774
}
775