Passed
Push — master ( ebfd49...ed3d70 )
by vistart
03:53
created

Organization::hasAdministrator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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