Test Failed
Push — master ( 8cc2e5...588d58 )
by vistart
06:55
created

Organization::getRemainingSubordinatePlaces()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

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