Test Failed
Push — master ( b8a5a5...3d0ef5 )
by vistart
04:28
created

Organization::onRevokeAdministrators()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
ccs 2
cts 2
cp 1
cc 2
eloc 5
nc 2
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\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
    protected function getNoInitMember()
94
    {
95
        if (!$this->noInitMember) {
96 24
            $class = $this->memberClass;
97
            $this->noInitMember = $class::buildNoInitModel();
98 24
        }
99 24
        return $this->noInitMember;
100 24
    }
101
102 24
    public function init()
103
    {
104
        $this->parentAttribute = 'parent_guid';
105 34
        if (class_exists($this->memberClass)) {
106
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
107 34
        }
108 34
        if ($this->skipInit) {
109 34
            return;
110
        }
111 34
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
112 34
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
113
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeCreator']);
114 34
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeAdministrators']);
115 34
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokePermissions']);
116 34
        $this->initSelfBlameableEvents();
117 34
        parent::init();
118 34
    }
119 34
120 34
    /**
121
     * @inheritdoc
122
     */
123
    public function attributeLabels()
124
    {
125 1
        return [
126
            'guid' => Yii::t('app', 'GUID'),
127
            'id' => Yii::t('app', 'ID'),
128 1
            'ip' => Yii::t('app', 'IP'),
129 1
            'ip_type' => Yii::t('app', 'IP Address Type'),
130 1
            'parent' => Yii::t('app', 'Parent'),
131 1
            'created_at' => Yii::t('app', 'Create Time'),
132 1
            'updated_at' => Yii::t('app', 'Update Time'),
133 1
            'status' => Yii::t('app', 'Status'),
134 1
            'type' => Yii::t('app', 'Type'),
135 1
        ];
136 1
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141
    public static function tableName()
142
    {
143 34
        return '{{%organization}}';
144
    }
145 34
146
    protected function getTypeRules()
147
    {
148 33
        return [
149
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
150
            ['type', 'required'],
151 33
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
152
        ];
153 33
    }
154
155
    public function rules()
156
    {
157 33
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
158
    }
159 33
160
    /**
161
     * Get Member Query.
162
     * @return MemberQuery
163
     */
164
    public function getMembers()
165
    {
166 24
        return $this->hasMany($this->memberClass, [$this->getNoInitMember()->createdByAttribute => $this->guidAttribute])->inverseOf('organization');
167
    }
168 24
169
    /**
170
     * Get organization member users' query.
171
     * @return BaseUserQuery
172
     */
173
    public function getMemberUsers()
174
    {
175 3
        $noInit = $this->getNoInitMember();
176
        $class = $noInit->memberUserClass;
177 3
        $noInitUser = $class::buildNoInitModel();
178 3
        return $this->hasMany($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('members')->inverseOf('atOrganizations');
179 3
    }
180 3
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
    public function getMember($user)
187
    {
188 19
        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 19
191
    /**
192
     * Add member to organization.
193
     * @param Member|User|string|integer $member
194
     * @see createMemberModel
195
     * @see createMemberModelWithUser
196
     * @return boolean
197
     */
198
    public function addMember(&$member)
199
    {
200 32
        if ($this->getIsNewRecord()) {
201
            return false;
202 32
        }
203
        $model = null;
204
        if ($member instanceof Member) {
205 32
            $model = $this->createMemberModel($member);
206 32
        }
207
        if (($member instanceof User) || is_string($member) || is_int($member)) {
208
            $model = $this->createMemberModelWithUser($member);
209 32
        }
210 32
        $member = $model;
211
        return ($member instanceof Member) ? $member->save() : false;
212 32
    }
213 32
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
    public function createMemberModelWithUser($user)
237
    {
238 32
        $config = [
239
            'memberUser' => $user,
240
            'organization' => $this,
241 32
            'nickname' => '',
242 32
        ];
243 32
        if ($user->profile) {
244
            $config['nickname'] = $user->profile->nickname;
245 32
        }
246 32
        return $this->createMember($config);
247
    }
248 32
249
    /**
250
     * Remove member.
251
     * @param Member|User $member
252
     * @return boolean
253
     */
254
    public function removeMember(&$member)
255
    {
256 1
        if ($this->getIsNewRecord()) {
257
            return false;
258 1
        }
259
        if ($member instanceof $this->memberClass) {
260
            $member = $member->{$member->memberAttribute};
261 1
        }
262 1
        $member = $this->getMember($member);
263
        return $member && $member->delete() > 0;
264 1
    }
265 1
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 33
     */
273
    public function removeAdministrator(&$member, $keepMember = true)
274 33
    {
275 33
        if ($this->getIsNewRecord()) {
276
            return false;
277
        }
278 33
        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 33
            }
286
            return $this->removeMember($member);
287 33
        }
288
        return false;
289
    }
290
291
    /**
292
     * 
293
     * @param Event $event
294 18
     */
295
    public function onAddProfile($event)
296 18
    {
297
        $profile = $event->sender->createProfile($event->data);
298 18
        if (!$profile->save()) {
299
            throw new IntegrityException('Profile Save Failed.');
300 18
        }
301 18
        return true;
302
    }
303
304
    /**
305
     * 
306
     * @param Event $event
307
     */
308 18
    public function onAssignCreator($event)
309
    {
310 18
        return $event->sender->addCreator($event->data);
311
    }
312 18
313
    /**
314 18
     * 
315 18
     * @param Event $event
316
     */
317 3
    public function onRevokeCreator($event)
318
    {
319 18
        $sender = $event->sender;
320
        /* @var $sender static */
321
        $member = $sender->getMemberCreators()->one();
322
        /* @var $member Member */
323
        $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
324
        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 18
    }
326
327
    /**
328 18
     * 
329
     * @param Event $event
330
     */
331
    public function onRevokeAdministrators($event)
332
    {
333
        $sender = $event->sender;
334 2
        /* @var $sender static */
335
        $members = $sender->getMemberAdministrators()->all();
336 2
        /* @var $members Member[] */
337
        foreach ($members as $member)
338
        {
339
            $member->revokeAdministrator();
340
        }
341
    }
342
343 2
    /**
344
     * 
345 2
     * @param Event $event
346
     */
347
    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
    }
351
352
    /**
353 6
     * 
354
     * @return boolean
355 6
     */
356
    public function isOrganization()
357
    {
358
        return $this->type == static::TYPE_ORGANIZATION;
359
    }
360
361
    /**
362 18
     * 
363
     * @return boolean
364 18
     */
365
    public function isDepartment()
366
    {
367
        return $this->type == static::TYPE_DEPARTMENT;
368
    }
369
370
    /**
371 18
     * Check whether the current organization has a member.
372
     * @param User $user
373 18
     * @return boolean
374
     */
375
    public function hasMember($user)
376
    {
377
        return !is_null($this->getMember($user));
378
    }
379
380 1
    /**
381
     * Get member query which role is specified `Creator`.
382 1
     * @return MemberQuery
383 1
     */
384 1
    public function getMemberCreators()
385 1
    {
386
        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 1
     */
393
    public function getMemberAdministrators()
394 1
    {
395 1
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
396 1
    }
397 1
398
    /**
399
     * Get user query which role is specified `Creator`.
400
     * @return BaseUserQuery
401
     */
402
    public function getCreator()
403
    {
404
        $noInit = $this->getNoInitMember();
405
        $class = $noInit->memberUserClass;
406
        $noInitUser = $class::buildNoInitModel();
407 33
        return $this->hasOne($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
408
    }
409 33
410 1
    /**
411
     * Get user query which role is specified `Administrator`.
412 32
     * @return BaseUserQuery
413 32
     */
414
    public function getAdministrators()
415 32
    {
416
        $noInit = $this->getNoInitMember();
417
        $class = $noInit->memberUserClass;
418 32
        $noInitUser = $class::buildNoInitModel();
419 32
        return $this->hasMany($class, [$noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
420 32
    }
421
422
    /**
423 32
     * 
424
     * @param User $user
425
     * @return boolean
426
     * @throws \Exception
427
     * @throws IntegrityException
428
     */
429 32
    protected function addCreator($user)
430
    {
431
        if (!$user) {
432
            throw new InvalidParamException('Creator Invalid.');
433
        }
434
        $member = $user;
435
        $transaction = Yii::$app->db->beginTransaction();
436
        try {
437
            if (!$this->addMember($member)) {
438
                throw new IntegrityException('Failed to add member.');
439 5
            }
440
            $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
441 5
            $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
            if (!$member->save()) {
443 5
                throw new IntegrityException('Failed to assign creator.');
444
            }
445
            $transaction->commit();
446 5
        } catch (\Exception $ex) {
447 5
            $transaction->rollBack();
448 5
            Yii::error($ex->getMessage(), __METHOD__);
449 5
            throw $ex;
450
        }
451
        return true;
452 5
    }
453
454
    /**
455
     * 
456
     * @param User $user
457
     * @return boolean
458 5
     * @throws \Exception
459
     * @throws IntegrityException
460
     */
461
    public function addAdministrator($user)
462
    {
463
        $transaction = Yii::$app->db->beginTransaction();
464
        try {
465
            if (!$this->hasMember($user) && !$this->addMember($user)) {
466 2
                throw new IntegrityException('Failed to add member.');
467
            }
468 2
            $member = $this->getMember($user);
469 2
            $member->assignAdministrator();
470
            $transaction->commit();
471
        } catch (\Exception $ex) {
472 2
            $transaction->rollBack();
473
            Yii::error($ex->getMessage(), __METHOD__);
474
            throw $ex;
475
        }
476
        return true;
477
    }
478
479
    /**
480
     * 
481
     * @param type $user
482
     * @return boolean
483
     */
484
    public function hasAdministrator($user)
485
    {
486
        $member = $this->getMember($user);
487
        if (!$member) {
488
            return false;
489
        }
490
        return $member->isAdministrator();
491
    }
492
}
493