Completed
Push — master ( d982bf...8c401a )
by vistart
24:10
created

Organization::getMemberLimit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 6
cp 0.8333
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 2.0185
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
    public $cacheTagPrefix = 'tag_organization_';
191
192
    /**
193
     * @return Member
194
     */
195 51
    public function getNoInitMember()
196 17
    {
197 51
        if (!$this->noInitMember) {
198 51
            $class = $this->memberClass;
199 51
            $this->noInitMember = $class::buildNoInitModel();
200 51
        }
201 51
        return $this->noInitMember;
202
    }
203
204
    /**
205
     * @return SubordinateLimit
206
     */
207 19
    public function getNoInitSubordinateLimit()
208 1
    {
209 2
        if (!$this->noInitSubordinateLimit) {
210 19
            $class = $this->subordinateLimitClass;
211 2
            $this->noInitSubordinateLimit = $class::buildNoInitModel();
212 2
        }
213 2
        return $this->noInitSubordinateLimit;
214
    }
215
216
    /**
217
     * @return MemberLimit
218
     */
219 1
    public function getNoInitMemberLimit()
220
    {
221 1
        if (!$this->noInitMemberLimit) {
222 1
            $class = $this->memberLimitClass;
223 1
            $this->noInitMemberLimit = $class::buildNoInitModel();
224 1
        }
225 1
        return $this->noInitMemberLimit;
226
    }
227
228
    /**
229
     * @return null|OrganizationSearch
230
     */
231
    public function getSearchModel()
232
    {
233
        $class = $this->searchClass;
234
        if (empty($class) || !class_exists($class)) {
235
            return null;
236
        }
237
        return new $class;
238
    }
239
240 52
    public function init()
241
    {
242 52
        $this->parentAttribute = 'parent_guid';
243 52
        if (class_exists($this->memberClass)) {
244 52
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
245 52
        }
246 52
        if ($this->skipInit) {
247 52
            return;
248
        }
249 52
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
250 52
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
251 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeCreator']);
252 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeAdministrators']);
253 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokePermissions']);
254 52
        $this->initSelfBlameableEvents();
255 52
        parent::init();
256 52
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261 19
    public function attributeLabels()
262
    {
263
        return [
264 1
            'guid' => Yii::t('user', 'GUID'),
265 19
            'id' => Yii::t('user', 'ID'),
266 1
            'ip' => Yii::t('user', 'IP Address'),
267 1
            'ip_type' => Yii::t('user', 'IP Address Type'),
268 1
            'parent' => Yii::t('organization', 'Parent'),
269 1
            'created_at' => Yii::t('user', 'Creation Time'),
270 1
            'updated_at' => Yii::t('user', 'Last Updated Time'),
271 1
            'status' => Yii::t('user', 'Status'),
272 1
            'type' => Yii::t('user', 'Type'),
273 1
            'isExcludeOtherMembers' => Yii::t('organization', 'Exclude Other Members'),
274 1
            'isDisallowMemberJoinOther' => Yii::t('organization', 'Disallow Member to Join in Other Organizations'),
275 1
            'isOnlyAcceptCurrentOrgMember' => Yii::t('organization', 'Only Accept Current Organization Members'),
276 1
            'isOnlyAcceptSuperiorOrgMember' => Yii::t('organization', 'Only Accept Superior Organization Members'),
277 1
        ];
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283 52
    public static function tableName()
284
    {
285 52
        return '{{%organization}}';
286
    }
287
288
    /**
289
     * Find.
290
     * Friendly to IDE.
291
     * @return OrganizationQuery
292
     */
293 52
    public static function find()
294
    {
295 52
        return parent::find();
296
    }
297
298 51
    protected function getTypeRules()
299
    {
300 1
        return [
301 51
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
302 51
            ['type', 'required'],
303 51
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
304 51
            [['eom', 'djo', 'oacm', 'oasm'], 'default', 'value' => 0],
305 51
        ];
306
    }
307
308 51
    public function rules()
309
    {
310 51
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
311
    }
312
313
    /**
314
     * Get Member Query.
315
     * @return MemberQuery
316
     */
317 50
    public function getMembers()
318
    {
319 50
        return $this->hasMany($this->memberClass, [
320 50
            $this->getNoInitMember()->createdByAttribute => $this->guidAttribute
321 50
        ])->inverseOf('organization');
322
    }
323
324
    /**
325
     * Get organization member users' query.
326
     * @return BaseUserQuery
327
     */
328 6
    public function getMemberUsers()
329
    {
330 6
        $noInit = $this->getNoInitMember();
331 6
        $class = $noInit->memberUserClass;
332 6
        $noInitUser = $class::buildNoInitModel();
333 6
        return $this->hasMany($class, [
334 6
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
335 6
        ])->via('members')->inverseOf('atOrganizations');
336
    }
337
338
    /**
339
     * Get subordinate limit query.
340
     * @return null|BaseBlameableQuery
341
     */
342 2
    public function getSubordinateLimit()
343
    {
344 2
        if (empty($this->subordinateLimitClass)) {
345
            return null;
346
        }
347 2
        return $this->hasOne($this->subordinateLimitClass, [
348 2
            $this->getNoInitSubordinateLimit()->createdByAttribute => $this->guidAttribute
349 2
        ]);
350
    }
351
352
    /**
353
     * Get member limit query.
354
     * @return null|BaseBlameableQuery
355
     */
356 1
    public function getMemberLimit()
357
    {
358 1
        if (empty($this->memberLimitClass)) {
359
            return null;
360
        }
361 1
        return $this->hasOne($this->memberLimitClass, [
362 1
            $this->getNoInitMemberLimit()->createdByAttribute => $this->guidAttribute
363 1
        ]);
364
    }
365
366
    /**
367
     * Get member with specified user.
368
     * @param User|string|integer $user
369
     * @return Member Null if `user` is not in this organization.
370
     */
371 50
    public function getMember($user)
372
    {
373 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 373 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
374
    }
375
376
    /**
377
     * Add member to organization.
378
     * @param Member|User|string|integer $member Member or User model, or User ID or GUID.
379
     * If member is created, it will be re-assigned to this parameter.
380
     * @see createMemberModel
381
     * @see createMemberModelWithUser
382
     * @return boolean
383
     * @throws DisallowMemberJoinOtherException
384
     * @throws ExcludeOtherMembersException
385
     * @throws OnlyAcceptCurrentOrgMemberException
386
     * @throws OnlyAcceptSuperiorOrgMemberException
387
     */
388 50
    public function addMember(&$member)
389
    {
390 50
        if ($this->getIsNewRecord()) {
391
            return false;
392
        }
393 50
        if ($this->hasReachedMemberLimit()) {
394 1
            return false;
395
        }
396 50
        $user = null;
397 50
        if ($member instanceof Member) {
398
            if ($member->getIsNewRecord()) {
399
                return false;
400
            }
401
            $user = $member->memberUser;
402
        }
403 50
        if ($member instanceof User) {
404 50
            $user = $member;
405 50
        }
406 50
        if (is_string($member) || is_int($member)) {
407
            $class = Yii::$app->user->identityClass;
408
            $user = $class::find()->guidOrId($member)->one();
409
        }
410 50
        if ($this->hasMember($user)) {
411
            return false;
412
        }
413 50
        $orgs = $user->getAtOrganizations()->all();
414
        /* @var $orgs Organization[] */
415 50
        foreach ($orgs as $org) {
416 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...
417 1
                throw new DisallowMemberJoinOtherException(Yii::t('organization', "An organization in which the user is located does not allow its members to join other organizations."));
418
            }
419 31
            if ($this->topOrganization->isExcludeOtherMembers && !$org->topOrganization->equals($this->topOrganization)) {
420 1
                throw new ExcludeOtherMembersException(Yii::t('organization', "The organization does not allow users who have joined other organizations to join."));
421
            }
422 50
        }
423 50
        if ($this->isDepartment() && $this->isOnlyAcceptCurrentOrgMember && !$this->topOrganization->hasMember($user)) {
424 1
            throw new OnlyAcceptCurrentOrgMemberException(Yii::t('organization' ,'This department is only accepted by members of the organization.'));
425
        }
426 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...
427 1
            throw new OnlyAcceptSuperiorOrgMemberException(Yii::t('organization', 'This department only accepts members of the parent organization or department.'));
428
        }
429
430 50
        $this->trigger(self::EVENT_BEFORE_ADD_MEMBER);
431 50
        $model = null;
432 50
        if ($member instanceof Member) {
433
            $model = $this->createMemberModel($member);
434 50
        } elseif (($member instanceof User) || is_string($member) || is_int($member)) {
435 50
            $model = $this->createMemberModelWithUser($member);
436 50
        }
437 50
        $member = $model;
438 50
        $result = ($member instanceof Member) ? $member->save() : false;
439 50
        $this->trigger(self::EVENT_AFTER_ADD_MEMBER);
440 50
        return $result;
441
    }
442
443
    /**
444
     * Create member model, and set organization with this.
445
     * @param Member $member If this parameter is not new record, it's organization
446
     * will be set with this, and return it. Otherwise, it will extract `User`
447
     * model and create new `Member` model.
448
     * @see createMemberModelWithUser
449
     * @return Member
450
     */
451
    public function createMemberModel($member)
452
    {
453
        if (!$member->getIsNewRecord()) {
454
            $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...
455
            return $member;
456
        }
457
        return $this->createMemberModelWithUser($member->memberUser);
458
    }
459
460
    /**
461
     * Create member model with user, and set organization with this.
462
     * @param User|string|integer $user
463
     * @return Member
464
     */
465 50
    public function createMemberModelWithUser($user)
466
    {
467
        $config = [
468 50
            'memberUser' => $user,
469 50
            'organization' => $this,
470 50
            'nickname' => '',
471 50
        ];
472 50
        $member = $this->createMember($config);
473 50
        $member->nickname = $member->memberUser->profile->nickname;
474 50
        return $member;
475
    }
476
477
    /**
478
     * Remove member.
479
     * Note: the creator cannot be removed.
480
     * @param Member|User $member
481
     * @return boolean
482
     */
483 4
    public function removeMember(&$member)
484
    {
485 4
        if ($this->getIsNewRecord()) {
486
            return false;
487
        }
488 4
        $this->trigger(self::EVENT_BEFORE_REMOVE_MEMBER);
489 4
        if ($member instanceof $this->memberClass) {
490 4
            $member = $member->{$member->memberAttribute};
491 4
        }
492 4
        $member = $this->getMember($member);
493 4
        if (!$member || $member->isCreator()) {
494
            return false;
495
        }
496 4
        $result = $member->delete() > 0;
497 4
        $this->trigger(self::EVENT_AFTER_REMOVE_MEMBER);
498 4
        return $result;
499
    }
500
501
    /**
502
     * Remove administrator.
503
     * @param Member|User|integer|string $member Member instance, or User instance or its GUID or ID.
504
     * @param boolean $keep Keep member after administrator being revoked.
505
     * @return boolean
506
     * @throws IntegrityException
507
     */
508
    public function removeAdministrator(&$member, $keep = true)
509
    {
510
        if ($this->getIsNewRecord()) {
511
            return false;
512
        }
513
        if ($member instanceof $this->memberClass) {
514
            $member = $member->{$member->memberAttribute};
515
        }
516
        $member = $this->getMember($member);
517
        if ($member && $member->isAdministrator()) {
518
            if ($keep) {
519
                return $member->revokeAdministrator();
520
            }
521
            return $this->removeMember($member);
522
        }
523
        return false;
524
    }
525
526
    /**
527
     * 
528
     * @param Event $event
529
     * @throws IntegrityException
530
     * @return boolean
531
     */
532 51
    public function onAddProfile($event)
533
    {
534 51
        $profile = $event->sender->createProfile($event->data);
535 51
        if (!$profile->save()) {
536
            throw new IntegrityException('Profile Save Failed.');
537
        }
538 51
        return true;
539
    }
540
541
    /**
542
     * 
543
     * @param Event $event
544
     */
545 51
    public function onAssignCreator($event)
546
    {
547 51
        return $event->sender->addCreator($event->data);
548
    }
549
550
    /**
551
     * 
552
     * @param Event $event
553
     * @return boolean
554
     */
555 20
    public function onRevokeCreator($event)
556
    {
557 20
        $sender = $event->sender;
558
        /* @var $sender static */
559 20
        $member = $sender->getMemberCreators()->one();
560
        /* @var $member Member */
561 20
        $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
562 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...
563
    }
564
565
    /**
566
     * 
567
     * @param Event $event
568
     * @return boolean
569
     */
570 20
    public function onRevokeAdministrators($event)
571
    {
572 20
        $sender = $event->sender;
573
        /* @var $sender static */
574 20
        $members = $sender->getMemberAdministrators()->all();
575
        /* @var $members Member[] */
576 20
        foreach ($members as $member)
577
        {
578 1
            $member->revokeAdministrator();
579 20
        }
580 20
        return true;
581
    }
582
583
    /**
584
     * 
585
     * @param Event $event
586
     */
587 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...
588
    {
589
        
590 20
    }
591
592
    /**
593
     * Check whether current instance is an organization.
594
     * @return boolean
595
     */
596 50
    public function isOrganization()
597
    {
598 50
        return $this->type == static::TYPE_ORGANIZATION;
599
    }
600
601
    /**
602
     * Check whether current instance if a department.
603
     * @return boolean
604
     */
605 50
    public function isDepartment()
606
    {
607 50
        return $this->type == static::TYPE_DEPARTMENT;
608
    }
609
610
    /**
611
     * Check whether the current organization has a member.
612
     * @param User|string|integer $user User instance, GUID or ID.
613
     * @return boolean
614
     */
615 50
    public function hasMember($user)
616
    {
617 50
        return !empty($this->getMember($user));
618
    }
619
620
    /**
621
     * Get member query which role is specified `Creator`.
622
     * @return MemberQuery
623
     */
624 24
    public function getMemberCreators()
625
    {
626 24
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
627
    }
628
629
    /**
630
     * Get member query which role is specified `Administrator`.
631
     * @return MemberQuery
632
     */
633 22
    public function getMemberAdministrators()
634
    {
635 22
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
636
    }
637
638
    /**
639
     * Get user query which role is specified `Creator`.
640
     * @return BaseUserQuery
641
     */
642 4
    public function getCreator()
643
    {
644 4
        $noInit = $this->getNoInitMember();
645 4
        $class = $noInit->memberUserClass;
646 4
        $noInitUser = $class::buildNoInitModel();
647 4
        return $this->hasOne($class, [
648 4
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
649 4
        ])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
650
    }
651
652
    /**
653
     * Get user query which role is specified `Administrator`.
654
     * @return BaseUserQuery
655
     */
656 2
    public function getAdministrators()
657
    {
658 2
        $noInit = $this->getNoInitMember();
659 2
        $class = $noInit->memberUserClass;
660 2
        $noInitUser = $class::buildNoInitModel();
661 2
        return $this->hasMany($class, [
662 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
663 2
        ])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
664
    }
665
666
    /**
667
     * 
668
     * @param User $user
669
     * @return boolean
670
     * @throws \Exception
671
     * @throws IntegrityException
672
     */
673 51
    protected function addCreator($user)
674
    {
675 51
        if (!$user) {
676 1
            throw new InvalidParamException('Creator Invalid.');
677
        }
678 50
        $member = $user;
679 50
        $transaction = Yii::$app->db->beginTransaction();
680
        try {
681 50
            if (!$this->addMember($member)) {
682
                throw new IntegrityException('Failed to add member.');
683
            }
684 50
            $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
685 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...
686 50
            if (!$member->save()) {
687
                throw new IntegrityException('Failed to assign creator.');
688
            }
689 50
            $transaction->commit();
690 50
        } catch (\Exception $ex) {
691
            $transaction->rollBack();
692
            Yii::error($ex->getMessage(), __METHOD__);
693
            throw $ex;
694
        }
695 50
        return true;
696
    }
697
698
    /**
699
     * Add administrator.
700
     * @param User|integer|string $user User instance, or its GUID or ID.
701
     * @return boolean
702
     * @throws \Exception
703
     * @throws IntegrityException
704
     */
705 17
    public function addAdministrator($user)
706
    {
707 17
        $transaction = Yii::$app->db->beginTransaction();
708
        try {
709 17
            if (!$this->hasMember($user) && !$this->addMember($user)) {
710
                throw new IntegrityException(Yii::t('organization', 'Failed to add member.'));
711
            }
712 17
            $member = $this->getMember($user);
713 17
            $member->assignAdministrator();
714 17
            $transaction->commit();
715 17
        } catch (\Exception $ex) {
716 2
            $transaction->rollBack();
717 2
            Yii::error($ex->getMessage(), __METHOD__);
718 2
            throw $ex;
719
        }
720 17
        return true;
721
    }
722
723
    /**
724
     * Check whether the current organization has administrator.
725
     * @param User|integer|string $user
726
     * @return boolean
727
     */
728 2
    public function hasAdministrator($user)
729
    {
730 2
        $member = $this->getMember($user);
731 2
        if (!$member) {
732
            return false;
733
        }
734 2
        return $member->isAdministrator();
735
    }
736
737
    /**
738
     * Check whether this organization has reached the upper limit of subordinates.
739
     * @return boolean
740
     */
741 19
    public function hasReachedSubordinateLimit()
742
    {
743 19
        $remaining = $this->getRemainingSubordinatePlaces();
744 19
        if ($remaining === false) {
745
            return false;
746
        }
747 19
        return $remaining <= 0;
748
    }
749
750
    /**
751
     * Get the remaining places of subordinates.
752
     * @return bool|int False if no limit
753
     */
754 19
    public function getRemainingSubordinatePlaces()
755
    {
756 19
        $class = $this->subordinateLimitClass;
757 19
        if (empty($class)) {
758
            return false;
759
        }
760 19
        $limit = $class::getLimit($this);
761 19
        if ($limit === false) {
762
            return false;
763
        }
764 19
        $count = (int)$this->getChildren()->count();
765 19
        return $limit - $count;
766
    }
767
768
    /**
769
     * Check whether this organization has reached the upper limit of members.
770
     * @return boolean
771
     */
772 50
    public function hasReachedMemberLimit()
773
    {
774 50
        $remaining = $this->getRemainingMemberPlaces();
775 50
        if ($remaining === false) {
776
            return false;
777
        }
778 50
        return $remaining <= 0;
779
    }
780
781
    /**
782
     * Get the remaining places of members.
783
     * @return bool|int False if no limit.
784
     */
785 50
    public function getRemainingMemberPlaces()
786
    {
787 50
        $class = $this->memberLimitClass;
788 50
        if (empty($class)) {
789
            return false;
790
        }
791 50
        $limit = $class::getLimit($this);
792 50
        if ($limit === false) {
793
            return false;
794
        }
795 50
        $count = (int)$this->getMembers()->count();
796 50
        return $limit - $count;
797
    }
798
799
    /**
800
     * @return bool
801
     */
802 31
    public function getIsExcludeOtherMembers()
803
    {
804 31
        return $this->eom > 0;
805
    }
806
807
    /**
808
     * @param bool $value
809
     */
810 2
    public function setIsExcludeOtherMembers($value = true)
811
    {
812 2
        $this->eom = ($value) ? 1 : 0;
813 2
    }
814
815
    /**
816
     * @return bool
817
     */
818 31
    public function getIsDisallowMemberJoinOther()
819
    {
820 31
        return $this->djo > 0;
821
    }
822
823
    /**
824
     * @param bool $value
825
     */
826 2
    public function setIsDisallowMemberJoinOther($value = true)
827
    {
828 2
        $this->djo = ($value) ? 1 : 0;
829 2
    }
830
831
    /**
832
     * @return bool
833
     */
834 18
    public function getIsOnlyAcceptCurrentOrgMember()
835
    {
836 18
        return $this->oacm > 0;
837
    }
838
839
    /**
840
     * @param bool $value
841
     */
842 2
    public function setIsOnlyAcceptCurrentOrgMember($value = true)
843
    {
844 2
        $this->oacm = ($value) ? 1 : 0;
845 2
    }
846
847
    /**
848
     * @return bool
849
     */
850 18
    public function getIsOnlyAcceptSuperiorOrgMember()
851
    {
852 18
        return $this->oasm > 0;
853
    }
854
855
    /**
856
     * @param bool $value
857
     */
858 2
    public function setIsOnlyAcceptSuperiorOrgMember($value = true)
859
    {
860 2
        $this->oasm = ($value) ? 1 : 0;
861 2
    }
862
863
    /**
864
     * @return $this|null|static
865
     */
866 31
    public function getTopOrganization()
867
    {
868 31
        if ($this->isOrganization()) {
869 31
            return $this;
870
        }
871 18
        $chain = $this->getAncestorChain();
872 18
        return static::findOne(end($chain));
873
    }
874
875
    /**
876
     * Check whether the subordinates have the [[$user]]
877
     * Note, this operation may consume the quantity of database selection.
878
     * @param User $user
879
     * @return bool
880
     */
881 2
    public function hasMemberInSubordinates($user)
882
    {
883 2
        if ($this->getChildren()->joinWith(['memberUsers mu_alias'])
884 2
            ->andWhere(['mu_alias.' . $user->guidAttribute => $user->getGUID()])->exists()) {
885 1
            return true;
886
        }
887 2
        $children = $this->children;
888
        /* @var $children static[] */
889 2
        foreach ($children as $child) {
890 2
            if ($child->hasMemberInSubordinates($user)) {
891 1
                return true;
892
            }
893 2
        }
894 2
        return false;
895
    }
896
}
897