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