Passed
Push — master ( 00c958...ca3e5a )
by vistart
04:09
created

Organization::getMemberCreators()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 34
    protected function getNoInitMember()
96
    {
97 34
        if (!$this->noInitMember) {
98 34
            $class = $this->memberClass;
99 34
            $this->noInitMember = $class::buildNoInitModel();
100
        }
101 34
        return $this->noInitMember;
102
    }
103
104 36
    public function init()
105
    {
106 36
        $this->parentAttribute = 'parent_guid';
107 36
        if (class_exists($this->memberClass)) {
108 36
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
109
        }
110 36
        if ($this->skipInit) {
111 36
            return;
112
        }
113 36
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
114 36
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
115 36
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeCreator']);
116 36
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeAdministrators']);
117 36
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokePermissions']);
118 36
        $this->initSelfBlameableEvents();
119 36
        parent::init();
120 36
    }
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 36
    public static function tableName()
144
    {
145 36
        return '{{%organization}}';
146
    }
147
148 35
    protected function getTypeRules()
149
    {
150
        return [
151 35
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
152
            ['type', 'required'],
153 35
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
154
        ];
155
    }
156
157 35
    public function rules()
158
    {
159 35
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
160
    }
161
162
    /**
163
     * Get Member Query.
164
     * @return MemberQuery
165
     */
166 34
    public function getMembers()
167
    {
168 34
        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 34
    public function getMember($user)
189
    {
190 34
        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 34
    public function addMember(&$member)
201
    {
202 34
        if ($this->getIsNewRecord()) {
203
            return false;
204
        }
205 34
        $model = null;
206 34
        if ($member instanceof Member) {
207
            if (!$member->getIsNewRecord()) {
208
                return false;
209
            }
210
            $model = $this->createMemberModel($member);
211
        }
212 34
        if (($member instanceof User) || is_string($member) || is_int($member)) {
213 34
            if ($this->hasMember($member)) {
214
                return false;
215
            }
216 34
            $model = $this->createMemberModelWithUser($member);
217
        }
218 34
        $member = $model;
219 34
        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 34
    public function createMemberModelWithUser($user)
245
    {
246
        $config = [
247 34
            'memberUser' => $user,
248 34
            'organization' => $this,
249 34
            'nickname' => '',
250
        ];
251 34
        $member = $this->createMember($config);
252 34
        $member->nickname = $member->memberUser->profile->nickname;
253 34
        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 35
    public function onAddProfile($event)
303
    {
304 35
        $profile = $event->sender->createProfile($event->data);
305 35
        if (!$profile->save()) {
306
            throw new IntegrityException('Profile Save Failed.');
307
        }
308 35
        return true;
309
    }
310
311
    /**
312
     * 
313
     * @param Event $event
314
     */
315 35
    public function onAssignCreator($event)
316
    {
317 35
        return $event->sender->addCreator($event->data);
318
    }
319
320
    /**
321
     * 
322
     * @param Event $event
323
     */
324 15
    public function onRevokeCreator($event)
325
    {
326 15
        $sender = $event->sender;
327
        /* @var $sender static */
328 15
        $member = $sender->getMemberCreators()->one();
329
        /* @var $member Member */
330 15
        $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
331 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...
332
    }
333
334
    /**
335
     * 
336
     * @param Event $event
337
     */
338 15
    public function onRevokeAdministrators($event)
339
    {
340 15
        $sender = $event->sender;
341
        /* @var $sender static */
342 15
        $members = $sender->getMemberAdministrators()->all();
343
        /* @var $members Member[] */
344 15
        foreach ($members as $member)
345
        {
346 1
            $member->revokeAdministrator();
347
        }
348 15
    }
349
350
    /**
351
     * 
352
     * @param Event $event
353
     */
354 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...
355
    {
356
        
357 15
    }
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 34
    public function hasMember($user)
383
    {
384 34
        return !is_null($this->getMember($user));
385
    }
386
387
    /**
388
     * Get member query which role is specified `Creator`.
389
     * @return MemberQuery
390
     */
391 16
    public function getMemberCreators()
392
    {
393 16
        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 16
    public function getMemberAdministrators()
401
    {
402 16
        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 35
    protected function addCreator($user)
437
    {
438 35
        if (!$user) {
439 1
            throw new InvalidParamException('Creator Invalid.');
440
        }
441 34
        $member = $user;
442 34
        $transaction = Yii::$app->db->beginTransaction();
443
        try {
444 34
            if (!$this->addMember($member)) {
445
                throw new IntegrityException('Failed to add member.');
446
            }
447 34
            $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
448 34
            $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 34
            if (!$member->save()) {
450
                throw new IntegrityException('Failed to assign creator.');
451
            }
452 34
            $transaction->commit();
453
        } catch (\Exception $ex) {
454
            $transaction->rollBack();
455
            Yii::error($ex->getMessage(), __METHOD__);
456
            throw $ex;
457
        }
458 34
        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