Passed
Push — master ( f14a0a...1212a9 )
by vistart
04:51
created

Organization::removeMember()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 7
cp 0.8571
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 5
nop 1
crap 4.0466
1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\organization;
14
15
use rhosocial\base\models\traits\SelfBlameableTrait;
16
use rhosocial\base\models\queries\BaseUserQuery;
17
use rhosocial\user\User;
18
use rhosocial\organization\rbac\roles\DepartmentAdmin;
19
use rhosocial\organization\rbac\roles\DepartmentCreator;
20
use rhosocial\organization\rbac\roles\OrganizationAdmin;
21
use rhosocial\organization\rbac\roles\OrganizationCreator;
22
use rhosocial\organization\queries\MemberQuery;
23
use Yii;
24
use yii\base\Event;
25
use yii\base\InvalidParamException;
26
use yii\db\IntegrityException;
27
28
/**
29
 * Base Organization.
30
 * This class is an abstract class that can not be instantiated directly.
31
 * You can use [[Organization]] or [[Department]] instead.
32
 *
33
 * @method Member createMember(array $config) Create member who is subordinate to this.
34
 * @property integer $type Whether indicate this instance is an organization or a department.
35
 *
36
 * @property-read User[] $memberUsers Get all members of this organization/department.
37
 * @property-read User $creator Get creator of this organization/department.
38
 * @property-read User[] $administrators Get administrators of this organization/department.
39
 *
40
 * @version 1.0
41
 * @author vistart <[email protected]>
42
 */
43
class Organization extends User
44
{
45
    use SelfBlameableTrait;
46
47
    const TYPE_ORGANIZATION = 1;
48
    const TYPE_DEPARTMENT = 2;
49
50
    /**
51
     * @var boolean Organization does not need password and corresponding features.
52
     */
53
    public $passwordHashAttribute = false;
54
55
    /**
56
     * @var boolean Organization does not need password and corresponding features.
57
     */
58
    public $passwordResetTokenAttribute = false;
59
60
    /**
61
     * @var boolean Organization does not need password and corresponding features.
62
     */
63
    public $passwordHistoryClass = false;
64
65
    /**
66
     * @var boolean Organization does not need source.
67
     */
68
    public $sourceAttribute = false;
69
70
    /**
71
     * @var boolean Organization does not need auth key.
72
     */
73
    public $authKeyAttribute = false;
74
75
    /**
76
     * @var boolean Organization does not need access token.
77
     */
78
    public $accessTokenAttribute = false;
79
80
    /**
81
     *
82
     * @var boolean Organization does not need login log.
83
     */
84
    public $loginLogClass = false;
85
86
    public $profileClass = Profile::class;
87
88
    public $memberClass = Member::class;
89
    private $noInitMember;
90
    public $creatorModel;
91
    public $profileConfig;
92
    /**
93
     * @return Member
94
     */
95 36
    protected function getNoInitMember()
96
    {
97 36
        if (!$this->noInitMember) {
98 36
            $class = $this->memberClass;
99 36
            $this->noInitMember = $class::buildNoInitModel();
100
        }
101 36
        return $this->noInitMember;
102
    }
103
104 38
    public function init()
105
    {
106 38
        $this->parentAttribute = 'parent_guid';
107 38
        if (class_exists($this->memberClass)) {
108 38
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
109
        }
110 38
        if ($this->skipInit) {
111 38
            return;
112
        }
113 38
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
114 38
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
115 38
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeCreator']);
116 38
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeAdministrators']);
117 38
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokePermissions']);
118 38
        $this->initSelfBlameableEvents();
119 38
        parent::init();
120 38
    }
121
122
    /**
123
     * @inheritdoc
124
     */
125 1
    public function attributeLabels()
126
    {
127
        return [
128 1
            'guid' => Yii::t('user', 'GUID'),
129 1
            'id' => Yii::t('user', 'ID'),
130 1
            'ip' => Yii::t('user', 'IP Address'),
131 1
            'ip_type' => Yii::t('user', 'IP Address Type'),
132 1
            'parent' => Yii::t('organization', 'Parent'),
133 1
            'created_at' => Yii::t('user', 'Creation Time'),
134 1
            'updated_at' => Yii::t('user', 'Last Updated Time'),
135 1
            'status' => Yii::t('user', 'Status'),
136 1
            'type' => Yii::t('user', 'Type'),
137
        ];
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143 38
    public static function tableName()
144
    {
145 38
        return '{{%organization}}';
146
    }
147
148 37
    protected function getTypeRules()
149
    {
150
        return [
151 37
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
152
            ['type', 'required'],
153 37
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
154
        ];
155
    }
156
157 37
    public function rules()
158
    {
159 37
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
160
    }
161
162
    /**
163
     * Get Member Query.
164
     * @return MemberQuery
165
     */
166 36
    public function getMembers()
167
    {
168 36
        return $this->hasMany($this->memberClass, [$this->getNoInitMember()->createdByAttribute => $this->guidAttribute])->inverseOf('organization');
169
    }
170
171
    /**
172
     * Get organization member users' query.
173
     * @return BaseUserQuery
174
     */
175 4
    public function getMemberUsers()
176
    {
177 4
        $noInit = $this->getNoInitMember();
178 4
        $class = $noInit->memberUserClass;
179 4
        $noInitUser = $class::buildNoInitModel();
180 4
        return $this->hasMany($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('members')->inverseOf('atOrganizations');
181
    }
182
183
    /**
184
     * Get member with specified user.
185
     * @param User|string|integer $user
186
     * @return Member Null if `user` is not in this organization.
187
     */
188 36
    public function getMember($user)
189
    {
190 36
        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 190 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
191
    }
192
193
    /**
194
     * Add member to organization.
195
     * @param Member|User|string|integer $member
196
     * @see createMemberModel
197
     * @see createMemberModelWithUser
198
     * @return boolean
199
     */
200 36
    public function addMember(&$member)
201
    {
202 36
        if ($this->getIsNewRecord()) {
203
            return false;
204
        }
205 36
        $model = null;
206 36
        if ($member instanceof Member) {
207
            if (!$member->getIsNewRecord()) {
208
                return false;
209
            }
210
            $model = $this->createMemberModel($member);
211
        }
212 36
        if (($member instanceof User) || is_string($member) || is_int($member)) {
213 36
            if ($this->hasMember($member)) {
214
                return false;
215
            }
216 36
            $model = $this->createMemberModelWithUser($member);
217
        }
218 36
        $member = $model;
219 36
        return ($member instanceof Member) ? $member->save() : false;
220
    }
221
222
    /**
223
     * Create member model, and set organization with this.
224
     * @param Member $member If this parameter is not new record, it's organization
225
     * will be set with this, and return it. Otherwise, it will extract `User`
226
     * model and create new `Member` model.
227
     * @see createMemberModelWithUser
228
     * @return Member
229
     */
230
    public function createMemberModel($member)
231
    {
232
        if (!$member->getIsNewRecord()) {
233
            $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...
234
            return $member;
235
        }
236
        return $this->createMemberModelWithUser($member->memberUser);
237
    }
238
239
    /**
240
     * Create member model with user, and set organization with this.
241
     * @param User|string|integer $user
242
     * @return Member
243
     */
244 36
    public function createMemberModelWithUser($user)
245
    {
246
        $config = [
247 36
            'memberUser' => $user,
248 36
            'organization' => $this,
249 36
            'nickname' => '',
250
        ];
251 36
        $member = $this->createMember($config);
252 36
        $member->nickname = $member->memberUser->profile->nickname;
253 36
        return $member;
254
    }
255
256
    /**
257
     * Remove member.
258
     * @param Member|User $member
259
     * @return boolean
260
     */
261 1
    public function removeMember(&$member)
262
    {
263 1
        if ($this->getIsNewRecord()) {
264
            return false;
265
        }
266 1
        if ($member instanceof $this->memberClass) {
267 1
            $member = $member->{$member->memberAttribute};
268
        }
269 1
        $member = $this->getMember($member);
270 1
        return $member && $member->delete() > 0;
271
    }
272
273
    /**
274
     * Remove administrator.
275
     * @param Member|User $member
276
     * @param boolean $keepMember Keep member after administrator being revoked.
277
     * @return boolean
278
     * @throws IntegrityException
279
     */
280
    public function removeAdministrator(&$member, $keepMember = true)
281
    {
282
        if ($this->getIsNewRecord()) {
283
            return false;
284
        }
285
        if ($member instanceof $this->memberClass) {
286
            $member = $member->{$member->memberAttribute};
287
        }
288
        $member = $this->getMember($member);
289
        if ($member && $member->isAdministrator()) {
290
            if ($keepMember) {
291
                return $member->revokeAdministrator();
292
            }
293
            return $this->removeMember($member);
294
        }
295
        return false;
296
    }
297
298
    /**
299
     * 
300
     * @param Event $event
301
     */
302 37
    public function onAddProfile($event)
303
    {
304 37
        $profile = $event->sender->createProfile($event->data);
305 37
        if (!$profile->save()) {
306
            throw new IntegrityException('Profile Save Failed.');
307
        }
308 37
        return true;
309
    }
310
311
    /**
312
     * 
313
     * @param Event $event
314
     */
315 37
    public function onAssignCreator($event)
316
    {
317 37
        return $event->sender->addCreator($event->data);
318
    }
319
320
    /**
321
     * 
322
     * @param Event $event
323
     */
324 17
    public function onRevokeCreator($event)
325
    {
326 17
        $sender = $event->sender;
327
        /* @var $sender static */
328 17
        $member = $sender->getMemberCreators()->one();
329
        /* @var $member Member */
330 17
        $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
331 17
        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...
332
    }
333
334
    /**
335
     * 
336
     * @param Event $event
337
     */
338 17
    public function onRevokeAdministrators($event)
339
    {
340 17
        $sender = $event->sender;
341
        /* @var $sender static */
342 17
        $members = $sender->getMemberAdministrators()->all();
343
        /* @var $members Member[] */
344 17
        foreach ($members as $member)
345
        {
346 1
            $member->revokeAdministrator();
347
        }
348 17
    }
349
350
    /**
351
     * 
352
     * @param Event $event
353
     */
354 17
    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...
355
    {
356
        
357 17
    }
358
359
    /**
360
     * Check whether current instance is an organization.
361
     * @return boolean
362
     */
363 2
    public function isOrganization()
364
    {
365 2
        return $this->type == static::TYPE_ORGANIZATION;
366
    }
367
368
    /**
369
     * Check whether current instance if a department.
370
     * @return boolean
371
     */
372 2
    public function isDepartment()
373
    {
374 2
        return $this->type == static::TYPE_DEPARTMENT;
375
    }
376
377
    /**
378
     * Check whether the current organization has a member.
379
     * @param User|string|integer $user User instance, GUID or ID.
380
     * @return boolean
381
     */
382 36
    public function hasMember($user)
383
    {
384 36
        return !is_null($this->getMember($user));
385
    }
386
387
    /**
388
     * Get member query which role is specified `Creator`.
389
     * @return MemberQuery
390
     */
391 18
    public function getMemberCreators()
392
    {
393 18
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
394
    }
395
396
    /**
397
     * Get member query which role is specified `Administrator`.
398
     * @return MemberQuery
399
     */
400 18
    public function getMemberAdministrators()
401
    {
402 18
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
403
    }
404
405
    /**
406
     * Get user query which role is specified `Creator`.
407
     * @return BaseUserQuery
408
     */
409 1
    public function getCreator()
410
    {
411 1
        $noInit = $this->getNoInitMember();
412 1
        $class = $noInit->memberUserClass;
413 1
        $noInitUser = $class::buildNoInitModel();
414 1
        return $this->hasOne($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
415
    }
416
417
    /**
418
     * Get user query which role is specified `Administrator`.
419
     * @return BaseUserQuery
420
     */
421 1
    public function getAdministrators()
422
    {
423 1
        $noInit = $this->getNoInitMember();
424 1
        $class = $noInit->memberUserClass;
425 1
        $noInitUser = $class::buildNoInitModel();
426 1
        return $this->hasMany($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
427
    }
428
429
    /**
430
     * 
431
     * @param User $user
432
     * @return boolean
433
     * @throws \Exception
434
     * @throws IntegrityException
435
     */
436 37
    protected function addCreator($user)
437
    {
438 37
        if (!$user) {
439 1
            throw new InvalidParamException('Creator Invalid.');
440
        }
441 36
        $member = $user;
442 36
        $transaction = Yii::$app->db->beginTransaction();
443
        try {
444 36
            if (!$this->addMember($member)) {
445
                throw new IntegrityException('Failed to add member.');
446
            }
447 36
            $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
448 36
            $member->assignRole($role);
0 ignored issues
show
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...
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...
449 36
            if (!$member->save()) {
450
                throw new IntegrityException('Failed to assign creator.');
451
            }
452 36
            $transaction->commit();
453
        } catch (\Exception $ex) {
454
            $transaction->rollBack();
455
            Yii::error($ex->getMessage(), __METHOD__);
456
            throw $ex;
457
        }
458 36
        return true;
459
    }
460
461
    /**
462
     * 
463
     * @param User $user
464
     * @return boolean
465
     * @throws \Exception
466
     * @throws IntegrityException
467
     */
468 7
    public function addAdministrator($user)
469
    {
470 7
        $transaction = Yii::$app->db->beginTransaction();
471
        try {
472 7
            if (!$this->hasMember($user) && !$this->addMember($user)) {
473
                throw new IntegrityException('Failed to add member.');
474
            }
475 7
            $member = $this->getMember($user);
476 7
            $member->assignAdministrator();
477 7
            $transaction->commit();
478
        } catch (\Exception $ex) {
479
            $transaction->rollBack();
480
            Yii::error($ex->getMessage(), __METHOD__);
481
            throw $ex;
482
        }
483 7
        return true;
484
    }
485
486
    /**
487
     * 
488
     * @param type $user
489
     * @return boolean
490
     */
491 2
    public function hasAdministrator($user)
492
    {
493 2
        $member = $this->getMember($user);
494 2
        if (!$member) {
495
            return false;
496
        }
497 2
        return $member->isAdministrator();
498
    }
499
}
500