Test Failed
Push — master ( be9b65...864bb4 )
by vistart
06:54
created

Organization::setJoinPassword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
ccs 0
cts 0
cp 0
cc 1
eloc 2
nc 1
nop 1
crap 2
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
 *
60
 * @property bool $isExcludeOtherMembers Determine whether the other organization and its subordinate departments
61
 * members could join in the current organization and its subordinate departments. (Only fit for Organization)
62
 * @property bool $isDisallowMemberJoinOther Determine whether the current organization and its subordinate
63
 * departments members could join in the other organization and its subordinate departments. (Only fit for Organization)
64
 * @property bool $isOnlyAcceptCurrentOrgMember Determine whether the current department only accept the member of
65
 * the top level organization. (Only fit for Department)
66
 * @property bool $isOnlyAcceptSuperiorOrgMember Determine whether the current department only accept the member of
67
 * the superior organization or department. (Only fit for Department)
68
 * @property string $joinPassword
69
 * @property string $joinIpAddress
70
 * @property string $joinEntranceUrl
71
 *
72
 * @property-read Member[] $members Get all member models of this organization/department.
73
 * @property-read User[] $memberUsers Get all members of this organization/department.
74
 * @property-read User $creator Get creator of this organization/department.
75
 * @property-read User[] $administrators Get administrators of this organization/department.
76
 * @property-read SubordinateLimit subordinateLimit
77
 * @property-read MemberLimit memberLimit
78
 * @property-read static|null $topOrganization The top level organization of current organization or departments.
79
 * @property-read Profile $profile Get profile model. Friendly to IDE.
80
 * @property-read OrganizationSetting[] $settings Get all settings.
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 string The Organization Setting Class
154
     */
155
    public $organizationSettingClass = OrganizationSetting::class;
156
157
    /**
158
     * @var Member
159
     */
160
    private $noInitMember;
161
162
    /**
163
     * @var SubordinateLimit
164
     */
165
    private $noInitSubordinateLimit;
166
167
    /**
168
     * @var MemberLimit
169
     */
170
    private $noInitMemberLimit;
171
172
    /**
173
     * @var OrganizationSetting
174
     */
175
    private $noInitOrganizationSetting;
176
177
    /**
178
     * @var User the creator of current Organization or Department.
179
     * This property is only available after registration.
180
     * Please do not access it at other times.
181
     * If you want to get creator model except registration, please
182
     * access [[$creator]] magic-property instead.
183
     */
184
    public $creatorModel;
185
186
    /**
187
     * @var array The configuration array of Organization Profile.
188
     * This property is only available after registration.
189
     * Please do not access it at other times.
190
     * If you want to get profile model except registration, please
191
     * access [[$profile]] magic-property instead.
192
     */
193
    public $profileConfig;
194
195
    const EVENT_BEFORE_ADD_MEMBER = 'eventBeforeAddMember';
196
    const EVENT_AFTER_ADD_MEMBER = 'eventAfterAddMember';
197
    const EVENT_BEFORE_REMOVE_MEMBER = 'eventBeforeRemoveMember';
198
    const EVENT_AFTER_REMOVE_MEMBER = 'eventAfterRemoveMember';
199
200
    public $cacheTagPrefix = 'tag_organization_';
201
202 51
    /**
203
     * @return Member
204 51
     */
205 51
    public function getNoInitMember()
206 51
    {
207
        if (!$this->noInitMember) {
208 51
            $class = $this->memberClass;
209
            $this->noInitMember = $class::buildNoInitModel();
210
        }
211
        return $this->noInitMember;
212
    }
213
214 2
    /**
215
     * @return SubordinateLimit
216 2
     */
217 2
    public function getNoInitSubordinateLimit()
218 2
    {
219
        if (!$this->noInitSubordinateLimit) {
220 2
            $class = $this->subordinateLimitClass;
221
            $this->noInitSubordinateLimit = $class::buildNoInitModel();
222
        }
223
        return $this->noInitSubordinateLimit;
224
    }
225
226 1
    /**
227
     * @return MemberLimit
228 1
     */
229 1
    public function getNoInitMemberLimit()
230 1
    {
231
        if (!$this->noInitMemberLimit) {
232 1
            $class = $this->memberLimitClass;
233
            $this->noInitMemberLimit = $class::buildNoInitModel();
234
        }
235
        return $this->noInitMemberLimit;
236
    }
237
238 31
    /**
239
     * @return null|OrganizationSetting
240 31
     */
241 31
    public function getNoInitOrganizationSetting()
242 31
    {
243
        if (!$this->noInitOrganizationSetting) {
244
            $class = $this->organizationSettingClass;
245 31
            if (empty($class)) {
246
                return null;
247 31
            }
248
            $this->noInitOrganizationSetting = $class::buildNoInitModel();
249
        }
250
        return $this->noInitOrganizationSetting;
251
    }
252
253
    /**
254
     * @return null|OrganizationSearch
255
     */
256
    public function getSearchModel()
257
    {
258
        $class = $this->searchClass;
259
        if (empty($class) || !class_exists($class)) {
260
            return null;
261
        }
262
        return new $class;
263
    }
264
265 52
    /**
266
     * @inheritdoc
267 52
     */
268 52
    public function init()
269 52
    {
270
        $this->parentAttribute = 'parent_guid';
271 52
        if (class_exists($this->memberClass)) {
272 52
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
273
        }
274 52
        if ($this->skipInit) {
275 52
            return;
276 52
        }
277 52
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
278 52
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
279 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeCreator']);
280 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeAdministrators']);
281 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokePermissions']);
282
        $this->initSelfBlameableEvents();
283
        parent::init();
284
    }
285
286 1
    /**
287
     * @inheritdoc
288
     */
289 1
    public function attributeLabels()
290 1
    {
291 1
        return [
292 1
            $this->guidAttribute => Yii::t('user', 'GUID'),
293 1
            $this->idAttribute => Yii::t('user', 'ID'),
294 1
            $this->ipAttribute => Yii::t('user', 'IP Address'),
295 1
            $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'),
296 1
            $this->parentAttribute => Yii::t('organization', 'Parent'),
297 1
            $this->createdAtAttribute => Yii::t('user', 'Creation Time'),
298 1
            $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'),
299 1
            $this->statusAttribute => Yii::t('user', 'Status'),
300 1
            'type' => Yii::t('user', 'Type'),
301 1
            'isExcludeOtherMembers' => Yii::t('organization', 'Exclude Other Members'),
302
            'isDisallowMemberJoinOther' => Yii::t('organization', 'Disallow Member to Join in Other Organizations'),
303
            'isOnlyAcceptCurrentOrgMember' => Yii::t('organization', 'Only Accept Current Organization Members'),
304
            'isOnlyAcceptSuperiorOrgMember' => Yii::t('organization', 'Only Accept Superior Organization Members'),
305
        ];
306
    }
307
308 52
    /**
309
     * @inheritdoc
310 52
     */
311
    public static function tableName()
312
    {
313
        return '{{%organization}}';
314
    }
315
316
    /**
317
     * Find.
318 52
     * Friendly to IDE.
319
     * @return OrganizationQuery
320 52
     */
321
    public static function find()
322
    {
323 51
        return parent::find();
324
    }
325
326 51
    /**
327
     * Get rules associated with type attribute.
328 51
     * @return array
329
     */
330
    protected function getTypeRules()
331
    {
332 51
        return [
333
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
334 51
            ['type', 'required'],
335
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
336
        ];
337
    }
338
339
    /**
340
     * @inheritdoc
341 50
     */
342
    public function rules()
343 50
    {
344 50
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
345 50
    }
346
347
    /**
348
     * Get Member Query.
349
     * @return MemberQuery
350
     */
351
    public function getMembers()
352 6
    {
353
        return $this->hasMany($this->memberClass, [
354 6
            $this->getNoInitMember()->createdByAttribute => $this->guidAttribute
355 6
        ])->inverseOf('organization');
356 6
    }
357 6
358 6
    /**
359 6
     * Get organization member users' query.
360
     * @return BaseUserQuery
361
     */
362
    public function getMemberUsers()
363
    {
364
        $noInit = $this->getNoInitMember();
365
        $class = $noInit->memberUserClass;
366 2
        $noInitUser = $class::buildNoInitModel();
367
        return $this->hasMany($class, [
368 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
369
        ])->via('members')->inverseOf('atOrganizations');
370
    }
371 2
372 2
    /**
373
     * Get subordinate limit query.
374
     * @return null|BaseBlameableQuery
375
     */
376
    public function getSubordinateLimit()
377
    {
378
        if (empty($this->subordinateLimitClass)) {
379
            return null;
380 1
        }
381
        return $this->hasOne($this->subordinateLimitClass, [
382 1
            $this->getNoInitSubordinateLimit()->createdByAttribute => $this->guidAttribute
383
        ]);
384
    }
385 1
386 1
    /**
387
     * Get member limit query.
388
     * @return null|BaseBlameableQuery
389
     */
390
    public function getMemberLimit()
391
    {
392
        if (empty($this->memberLimitClass)) {
393
            return null;
394 31
        }
395
        return $this->hasOne($this->memberLimitClass, [
396 31
            $this->getNoInitMemberLimit()->createdByAttribute => $this->guidAttribute
397
        ]);
398
    }
399 31
400 31
    /**
401 31
     * @param string|null $item If you want to get all settings, please set it null.
402
     * @return null
403 31
     */
404
    public function getSettings($item = null)
405
    {
406
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
407
            return null;
408
        }
409
        $query = $this->hasMany($this->organizationSettingClass, [$this->getNoInitOrganizationSetting()->createdByAttribute => $this->guidAttribute]);
410
        if (!empty($item)) {
411 31
            $query = $query->andWhere([$this->getNoInitOrganizationSetting()->idAttribute => $item]);
412
        }
413 31
        return $query;
414
    }
415
416 31
    /**
417
     * @param $item
418 31
     * @param $value
419 31
     * @return bool|null Null if organization setting not enabled.
420 31
     */
421
    public function setSetting($item, $value)
422
    {
423 31
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
424 31
            return null;
425
        }
426
        $setting = $this->getSettings($item)->one();
427
        /* @var $setting OrganizationSetting */
428
        if (!$setting) {
429
            $setting = $this->create($this->organizationSettingClass, [
430
                $this->getNoInitOrganizationSetting()->idAttribute => $item,
431
            ]);
432 50
        }
433
        $setting->value = $value;
434 50
        return $setting->save();
435
    }
436
437
    /**
438
     * Get member with specified user.
439
     * @param User|string|integer $user
440
     * @return Member Null if `user` is not in this organization.
441
     */
442
    public function getMember($user)
443
    {
444
        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 444 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
445
    }
446
447
    /**
448
     * Add member to organization.
449 50
     * @param Member|User|string|integer $member Member or User model, or User ID or GUID.
450
     * If member is created, it will be re-assigned to this parameter.
451 50
     * @see createMemberModel
452
     * @see createMemberModelWithUser
453
     * @return boolean
454 50
     * @throws DisallowMemberJoinOtherException
455 1
     * @throws ExcludeOtherMembersException
456
     * @throws OnlyAcceptCurrentOrgMemberException
457 50
     * @throws OnlyAcceptSuperiorOrgMemberException
458 50
     */
459
    public function addMember(&$member)
460
    {
461
        if ($this->getIsNewRecord()) {
462
            return false;
463
        }
464 50
        if ($this->hasReachedMemberLimit()) {
465 50
            return false;
466
        }
467 50
        $user = null;
468
        if ($member instanceof Member) {
469
            if ($member->getIsNewRecord()) {
470
                return false;
471 50
            }
472
            $user = $member->memberUser;
473
        }
474 50
        if ($member instanceof User) {
475
            $user = $member;
476 50
        }
477 31
        if (is_string($member) || is_int($member)) {
478 1
            $class = Yii::$app->user->identityClass;
479
            $user = $class::find()->guidOrId($member)->one();
480 31
        }
481 31
        if ($this->hasMember($user)) {
482
            return false;
483
        }
484 50
        $orgs = $user->getAtOrganizations()->all();
485 1
        /* @var $orgs Organization[] */
486
        foreach ($orgs as $org) {
487 50
            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...
488 1
                throw new DisallowMemberJoinOtherException(Yii::t('organization', "An organization in which the user is located does not allow its members to join other organizations."));
489
            }
490
            if ($this->topOrganization->isExcludeOtherMembers && !$org->topOrganization->equals($this->topOrganization)) {
491 50
                throw new ExcludeOtherMembersException(Yii::t('organization', "The organization does not allow users who have joined other organizations to join."));
492 50
            }
493 50
        }
494
        if ($this->isDepartment() && $this->isOnlyAcceptCurrentOrgMember && !$this->topOrganization->hasMember($user)) {
495 50
            throw new OnlyAcceptCurrentOrgMemberException(Yii::t('organization' ,'This department is only accepted by members of the organization.'));
496 50
        }
497
        if ($this->isDepartment() && !$this->parent->equals($this->topOrganization) && $this->isOnlyAcceptSuperiorOrgMember && !$this->parent->hasMember($user)) {
0 ignored issues
show
Bug introduced by
It seems like equals() 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...
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...
498 50
            throw new OnlyAcceptSuperiorOrgMemberException(Yii::t('organization', 'This department only accepts members of the parent organization or department.'));
499 50
        }
500 50
501 50
        $this->trigger(self::EVENT_BEFORE_ADD_MEMBER);
502
        $model = null;
503
        if ($member instanceof Member) {
504
            $model = $this->createMemberModel($member);
505
        } elseif (($member instanceof User) || is_string($member) || is_int($member)) {
506
            $model = $this->createMemberModelWithUser($member);
507
        }
508
        $member = $model;
509
        $result = ($member instanceof Member) ? $member->save() : false;
510
        $this->trigger(self::EVENT_AFTER_ADD_MEMBER);
511
        return $result;
512
    }
513
514
    /**
515
     * Create member model, and set organization with this.
516
     * @param Member $member If this parameter is not new record, it's organization
517
     * will be set with this, and return it. Otherwise, it will extract `User`
518
     * model and create new `Member` model.
519
     * @see createMemberModelWithUser
520
     * @return Member
521
     */
522
    public function createMemberModel($member)
523
    {
524
        if (!$member->getIsNewRecord()) {
525
            $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...
526 50
            return $member;
527
        }
528
        return $this->createMemberModelWithUser($member->memberUser);
529 50
    }
530 50
531 50
    /**
532
     * Create member model with user, and set organization with this.
533 50
     * @param User|string|integer $user
534 50
     * @return Member
535 50
     */
536
    public function createMemberModelWithUser($user)
537
    {
538
        $config = [
539
            'memberUser' => $user,
540
            'organization' => $this,
541
            'nickname' => '',
542
        ];
543
        $member = $this->createMember($config);
544 4
        $member->nickname = $member->memberUser->profile->nickname;
545
        return $member;
546 4
    }
547
548
    /**
549 4
     * Remove member.
550 4
     * Note: the creator cannot be removed.
551 4
     * @param Member|User $member
552
     * @return boolean
553 4
     */
554 4
    public function removeMember(&$member)
555
    {
556
        if ($this->getIsNewRecord()) {
557 4
            return false;
558 4
        }
559 4
        $this->trigger(self::EVENT_BEFORE_REMOVE_MEMBER);
560
        if ($member instanceof $this->memberClass) {
561
            $member = $member->{$member->memberAttribute};
562
        }
563
        $member = $this->getMember($member);
564
        if (!$member || $member->isCreator()) {
565
            return false;
566
        }
567
        $result = $member->delete() > 0;
568
        $this->trigger(self::EVENT_AFTER_REMOVE_MEMBER);
569
        return $result;
570
    }
571
572
    /**
573
     * Remove administrator.
574
     * @param Member|User|integer|string $member Member instance, or User instance or its GUID or ID.
575
     * @param boolean $keep Keep member after administrator being revoked.
576
     * @return boolean
577
     * @throws IntegrityException
578
     */
579
    public function removeAdministrator(&$member, $keep = true)
580
    {
581
        if ($this->getIsNewRecord()) {
582
            return false;
583
        }
584
        if ($member instanceof $this->memberClass) {
585
            $member = $member->{$member->memberAttribute};
586
        }
587
        $member = $this->getMember($member);
588
        if ($member && $member->isAdministrator()) {
589
            if ($keep) {
590
                return $member->revokeAdministrator();
591
            }
592
            return $this->removeMember($member);
593 51
        }
594
        return false;
595 51
    }
596 51
597
    /**
598
     * 
599 51
     * @param Event $event
600
     * @throws IntegrityException
601
     * @return boolean
602
     */
603
    public function onAddProfile($event)
604
    {
605
        $profile = $event->sender->createProfile($event->data);
606 51
        if (!$profile->save()) {
607
            throw new IntegrityException('Profile Save Failed.');
608 51
        }
609
        return true;
610
    }
611
612
    /**
613
     * 
614
     * @param Event $event
615
     */
616 20
    public function onAssignCreator($event)
617
    {
618 20
        return $event->sender->addCreator($event->data);
619
    }
620 20
621
    /**
622 20
     * 
623 20
     * @param Event $event
624
     * @return boolean
625
     */
626
    public function onRevokeCreator($event)
627
    {
628
        $sender = $event->sender;
629
        /* @var $sender static */
630
        $member = $sender->getMemberCreators()->one();
631 20
        /* @var $member Member */
632
        $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
633 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...
634
    }
635 20
636
    /**
637 20
     * 
638
     * @param Event $event
639 1
     * @return boolean
640
     */
641 20
    public function onRevokeAdministrators($event)
642
    {
643
        $sender = $event->sender;
644
        /* @var $sender static */
645
        $members = $sender->getMemberAdministrators()->all();
646
        /* @var $members Member[] */
647
        foreach ($members as $member)
648 20
        {
649
            $member->revokeAdministrator();
650
        }
651 20
        return true;
652
    }
653
654
    /**
655
     * 
656
     * @param Event $event
657 50
     */
658
    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...
659 50
    {
660
        
661
    }
662
663
    /**
664
     * Check whether current instance is an organization.
665
     * @return boolean
666 50
     */
667
    public function isOrganization()
668 50
    {
669
        return $this->type == static::TYPE_ORGANIZATION;
670
    }
671
672
    /**
673
     * Check whether current instance if a department.
674
     * @return boolean
675
     */
676 50
    public function isDepartment()
677
    {
678 50
        return $this->type == static::TYPE_DEPARTMENT;
679
    }
680
681
    /**
682
     * Check whether the current organization has a member.
683
     * @param User|string|integer $user User instance, GUID or ID.
684
     * @return boolean
685 24
     */
686
    public function hasMember($user)
687 24
    {
688
        return !empty($this->getMember($user));
689
    }
690
691
    /**
692
     * Get member query which role is specified `Creator`.
693
     * @return MemberQuery
694 22
     */
695
    public function getMemberCreators()
696 22
    {
697
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
698
    }
699
700
    /**
701
     * Get member query which role is specified `Administrator`.
702
     * @return MemberQuery
703 4
     */
704
    public function getMemberAdministrators()
705 4
    {
706 4
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
707 4
    }
708 4
709 4
    /**
710 4
     * Get user query which role is specified `Creator`.
711
     * @return BaseUserQuery
712
     */
713
    public function getCreator()
714
    {
715
        $noInit = $this->getNoInitMember();
716
        $class = $noInit->memberUserClass;
717 2
        $noInitUser = $class::buildNoInitModel();
718
        return $this->hasOne($class, [
719 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
720 2
        ])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
721 2
    }
722 2
723 2
    /**
724 2
     * Get user query which role is specified `Administrator`.
725
     * @return BaseUserQuery
726
     */
727
    public function getAdministrators()
728
    {
729
        $noInit = $this->getNoInitMember();
730
        $class = $noInit->memberUserClass;
731
        $noInitUser = $class::buildNoInitModel();
732
        return $this->hasMany($class, [
733
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
734 51
        ])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
735
    }
736 51
737 1
    /**
738
     * 
739 50
     * @param User $user
740 50
     * @return boolean
741
     * @throws \Exception
742 50
     * @throws IntegrityException
743
     */
744
    protected function addCreator($user)
745 50
    {
746 50
        if (!$user) {
747 50
            throw new InvalidParamException('Creator Invalid.');
748
        }
749
        $member = $user;
750 50
        $transaction = Yii::$app->db->beginTransaction();
751
        try {
752
            if (!$this->addMember($member)) {
753
                throw new IntegrityException('Failed to add member.');
754
            }
755
            $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
756 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...
757
            if (!$member->save()) {
758
                throw new IntegrityException('Failed to assign creator.');
759
            }
760
            $transaction->commit();
761
        } catch (\Exception $ex) {
762
            $transaction->rollBack();
763
            Yii::error($ex->getMessage(), __METHOD__);
764
            throw $ex;
765
        }
766 17
        return true;
767
    }
768 17
769
    /**
770 17
     * Add administrator.
771
     * @param User|integer|string $user User instance, or its GUID or ID.
772
     * @return boolean
773 17
     * @throws \Exception
774 17
     * @throws IntegrityException
775 17
     */
776 2
    public function addAdministrator($user)
777 2
    {
778 2
        $transaction = Yii::$app->db->beginTransaction();
779 2
        try {
780
            if (!$this->hasMember($user) && !$this->addMember($user)) {
781 17
                throw new IntegrityException(Yii::t('organization', 'Failed to add member.'));
782
            }
783
            $member = $this->getMember($user);
784
            $member->assignAdministrator();
785
            $transaction->commit();
786
        } catch (\Exception $ex) {
787
            $transaction->rollBack();
788
            Yii::error($ex->getMessage(), __METHOD__);
789 2
            throw $ex;
790
        }
791 2
        return true;
792 2
    }
793
794
    /**
795 2
     * Check whether the current organization has administrator.
796
     * @param User|integer|string $user
797
     * @return boolean
798
     */
799
    public function hasAdministrator($user)
800
    {
801
        $member = $this->getMember($user);
802 19
        if (!$member) {
803
            return false;
804 19
        }
805 19
        return $member->isAdministrator();
806
    }
807
808 19
    /**
809
     * Check whether this organization has reached the upper limit of subordinates.
810
     * @return boolean
811
     */
812
    public function hasReachedSubordinateLimit()
813
    {
814
        $remaining = $this->getRemainingSubordinatePlaces();
815 19
        if ($remaining === false) {
816
            return false;
817 19
        }
818 19
        return $remaining <= 0;
819
    }
820
821 19
    /**
822 19
     * Get the remaining places of subordinates.
823
     * @return bool|int False if no limit
824
     */
825 19
    public function getRemainingSubordinatePlaces()
826 19
    {
827
        $class = $this->subordinateLimitClass;
828
        if (empty($class)) {
829
            return false;
830
        }
831
        $limit = $class::getLimit($this);
832
        if ($limit === false) {
833 50
            return false;
834
        }
835 50
        $count = (int)$this->getChildren()->count();
836 50
        return $limit - $count;
837
    }
838
839 50
    /**
840
     * Check whether this organization has reached the upper limit of members.
841
     * @return boolean
842
     */
843
    public function hasReachedMemberLimit()
844
    {
845
        $remaining = $this->getRemainingMemberPlaces();
846 50
        if ($remaining === false) {
847
            return false;
848 50
        }
849 50
        return $remaining <= 0;
850
    }
851
852 50
    /**
853 50
     * Get the remaining places of members.
854
     * @return bool|int False if no limit.
855
     */
856 50
    public function getRemainingMemberPlaces()
857 50
    {
858
        $class = $this->memberLimitClass;
859
        if (empty($class)) {
860
            return false;
861
        }
862
        $limit = $class::getLimit($this);
863
        if ($limit === false) {
864
            return false;
865 31
        }
866
        $count = (int)$this->getMembers()->count();
867 31
        return $limit - $count;
868 31
    }
869 31
870 31
    const SETTING_ITEM_EXCLUDE_OTHER_MEMBERS = 'exclude_other_members';
871
872 31
    /**
873
     * @return bool
874
     */
875
    public function getIsExcludeOtherMembers()
876
    {
877
        $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
878
        if (!$setting) {
879 2
            $this->setSetting(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS, '0');
880
            $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
881 2
        }
882
        return $setting->value == '1';
883
    }
884
885
    /**
886
     * @param bool $value
887
     * @return bool
888
     */
889 31
    public function setIsExcludeOtherMembers($value = true)
890
    {
891 31
        return $this->setSetting(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS, $value ? '1' : '0');
892 31
    }
893 31
894 31
    const SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER = 'disallow_member_join_other';
895
896 31
    /**
897
     * @return bool
898
     */
899
    public function getIsDisallowMemberJoinOther()
900
    {
901
        $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
902
        if (!$setting) {
903 2
            $this->setSetting(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER, '0');
904
            $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
905 2
        }
906
        return $setting->value == '1';
907
    }
908
909
    /**
910
     * @param bool $value
911
     * @return bool
912
     */
913 18
    public function setIsDisallowMemberJoinOther($value = true)
914
    {
915 18
        return $this->setSetting(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER, $value ? '1' : '0');
916 18
    }
917 18
918 18
    const SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER = 'only_accept_current_org_member';
919
920 18
    /**
921
     * @return bool
922
     */
923
    public function getIsOnlyAcceptCurrentOrgMember()
924
    {
925
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
926
        if (!$setting) {
927 2
            $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER, '0');
928
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
929 2
        }
930
        return $setting->value == '1';
931
    }
932
933
    /**
934
     * @param bool $value
935
     * @return bool
936
     */
937 18
    public function setIsOnlyAcceptCurrentOrgMember($value = true)
938
    {
939 18
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER, $value ? '1' : '0');
940 18
    }
941 18
942 18
    const SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER = 'only_accept_superior_org_member';
943
944 18
    /**
945
     * @return bool
946
     */
947
    public function getIsOnlyAcceptSuperiorOrgMember()
948
    {
949
        if ($this->parent->equals($this->topOrganization)) {
0 ignored issues
show
Bug introduced by
It seems like equals() 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...
950
            return $this->getIsOnlyAcceptCurrentOrgMember();
951 2
        }
952
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
953 2
        if (!$setting) {
954
            $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER, '0');
955
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
956
        }
957
        return $setting->value == '1';
958
    }
959 31
960
    /**
961 31
     * @param bool $value
962 31
     * @return bool
963
     */
964 18
    public function setIsOnlyAcceptSuperiorOrgMember($value = true)
965 18
    {
966
        if ($this->parent->equals($this->topOrganization)) {
0 ignored issues
show
Bug introduced by
It seems like equals() 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...
967
            return $this->setIsOnlyAcceptCurrentOrgMember($value);
968
        }
969
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER, $value ? '1' : '0');
970
    }
971
972
    const SETTING_ITEM_JOIN_PASSWORD = 'join_password';
973
974 2
    /**
975
     * Get join password.
976 2
     * @return mixed
977 2
     */
978 1
    public function getJoinPassword()
979
    {
980 2
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_PASSWORD)->one();
981
        if (!$setting) {
982 2
            $this->setSetting(static::SETTING_ITEM_JOIN_PASSWORD, '');
983 2
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_PASSWORD)->one();
984 2
        }
985
        return $setting->value;
986
    }
987 2
988
    /**
989
     * Set join password.
990
     * @param string $value
991
     * @return bool|null
992
     */
993
    public function setJoinPassword($value = '')
994
    {
995
        return $this->setSetting(static::SETTING_ITEM_JOIN_PASSWORD, $value);
996
    }
997
998
    const SETTING_ITEM_JOIN_IP_ADDRESS = 'join_ip_address';
999
1000
    /**
1001
     * Get Join IP address
1002
     * @return mixed
1003
     */
1004
    public function getJoinIpAddress()
1005
    {
1006
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_IP_ADDRESS)->one();
1007
        if (!$setting) {
1008
            $this->setSetting(static::SETTING_ITEM_JOIN_IP_ADDRESS, '');
1009
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_IP_ADDRESS)->one();
1010
        }
1011
        return $setting->value;
1012
    }
1013
1014
    /**
1015
     * Set join IP address.
1016
     * @param $value
1017
     * @return bool|null
1018
     */
1019
    public function setJoinIpAddress($value = '')
1020
    {
1021
        return $this->setSetting(static::SETTING_ITEM_JOIN_IP_ADDRESS, $value);
1022
    }
1023
1024
    const SETTING_ITEM_JOIN_ENTRANCE_URL = 'join_entrance_url';
1025
1026
    /**
1027
     * Get join entrance URL.
1028
     * @return string
1029
     */
1030
    public function getJoinEntranceUrl()
1031
    {
1032
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_ENTRANCE_URL)->one();
1033
        if (!$setting) {
1034
            $this->setSetting(static::SETTING_ITEM_JOIN_ENTRANCE_URL, '');
1035
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_ENTRANCE_URL)->one();
1036
        }
1037
        return $setting->value;
1038
    }
1039
1040
    /**
1041
     * Set join entrance URL.
1042
     * @param string $value
1043
     * @return bool|null
1044
     */
1045
    public function setJoinEntranceUrl($value = '')
1046
    {
1047
        return $this->setSetting(static::SETTING_ITEM_JOIN_ENTRANCE_URL, $value);
1048
    }
1049
1050
    /**
1051
     * @return $this|null|static
1052
     */
1053
    public function getTopOrganization()
1054
    {
1055
        if ($this->isOrganization()) {
1056
            return $this;
1057
        }
1058
        $chain = $this->getAncestorChain();
1059
        return static::findOne(end($chain));
1060
    }
1061
1062
    /**
1063
     * Check whether the subordinates have the [[$user]]
1064
     * Note, this operation may consume the quantity of database selection.
1065
     * @param User $user
1066
     * @return bool
1067
     */
1068
    public function hasMemberInSubordinates($user)
1069
    {
1070
        if ($this->getChildren()->joinWith(['memberUsers mu_alias'])
1071
            ->andWhere(['mu_alias.' . $user->guidAttribute => $user->getGUID()])->exists()) {
1072
            return true;
1073
        }
1074
        $children = $this->children;
1075
        /* @var $children static[] */
1076
        foreach ($children as $child) {
1077
            if ($child->hasMemberInSubordinates($user)) {
1078
                return true;
1079
            }
1080
        }
1081
        return false;
1082
    }
1083
}
1084