Passed
Push — master ( 6e4772...b11769 )
by vistart
04:42
created

Organization::onRevokeAdministrators()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 7
cp 0.8571
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 4
nop 1
crap 3.0261
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
 
39
 * @version 1.0
40
 * @author vistart <[email protected]>
41
 */
42
class Organization extends User
43
{
44
    use SelfBlameableTrait;
45
46
    const TYPE_ORGANIZATION = 1;
47
    const TYPE_DEPARTMENT = 2;
48
49
    /**
50
     * @var boolean Organization does not need password and corresponding features.
51
     */
52
    public $passwordHashAttribute = false;
53
54
    /**
55
     * @var boolean Organization does not need password and corresponding features.
56
     */
57
    public $passwordResetTokenAttribute = false;
58
59
    /**
60
     * @var boolean Organization does not need password and corresponding features.
61
     */
62
    public $passwordHistoryClass = false;
63
64
    /**
65
     * @var boolean Organization does not need source.
66
     */
67
    public $sourceAttribute = false;
68
69
    /**
70
     * @var boolean Organization does not need auth key.
71
     */
72
    public $authKeyAttribute = false;
73
74
    /**
75
     * @var boolean Organization does not need access token.
76
     */
77
    public $accessTokenAttribute = false;
78
79
    /**
80
     *
81
     * @var boolean Organization does not need login log.
82
     */
83
    public $loginLogClass = false;
84
85
    public $profileClass = Profile::class;
86
87
    public $memberClass = Member::class;
88
    private $noInitMember;
89
    public $creator;
90
    public $profileConfig;
91
    /**
92
     * @return Member
93
     */
94 16
    protected function getNoInitMember()
95
    {
96 16
        if (!$this->noInitMember) {
97 16
            $class = $this->memberClass;
98 16
            $this->noInitMember = $class::buildNoInitModel();
99
        }
100 16
        return $this->noInitMember;
101
    }
102
103 25
    public function init()
104
    {
105 25
        $this->parentAttribute = 'parent_guid';
106 25
        if (class_exists($this->memberClass)) {
107 25
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
108
        }
109 25
        if ($this->skipInit) {
110 25
            return;
111
        }
112 25
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
113 25
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creator);
114 25
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeCreator']);
115 25
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokeAdministrators']);
116 25
        $this->on(static::$eventBeforeDeregister, [$this, 'onRevokePermissions']);
117 25
        parent::init();
118 25
    }
119
120
    /**
121
     * @inheritdoc
122
     */
123 1
    public function attributeLabels()
124
    {
125
        return [
126 1
            'guid' => Yii::t('app', 'GUID'),
127 1
            '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
        ];
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141 25
    public static function tableName()
142
    {
143 25
        return '{{%organization}}';
144
    }
145
146 24
    protected function getTypeRules()
147
    {
148
        return [
149 24
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
150
            ['type', 'required'],
151 24
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
152
        ];
153
    }
154
155 24
    public function rules()
156
    {
157 24
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
158
    }
159
160
    /**
161
     * Get Member Query.
162
     * @return MemberQuery
163
     */
164 16
    public function getMembers()
165
    {
166 16
        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 2
    public function getMemberUsers()
174
    {
175 2
        $noInit = $this->getNoInitMember();
176 2
        $class = $noInit->memberUserClass;
177 2
        $noInitUser = $class::buildNoInitModel();
178 2
        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 7
    public function getMember($user)
187
    {
188 7
        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 23
    public function addMember(&$member)
199
    {
200 23
        if ($this->getIsNewRecord()) {
201
            return false;
202
        }
203 23
        $model = null;
204 23
        if ($member instanceof Member) {
205
            $model = $this->createMemberModel($member);
206
        }
207 23
        if (($member instanceof User) || is_string($member) || is_int($member)) {
208 23
            $model = $this->createMemberModelWithUser($member);
209
        }
210 23
        $member = $model;
211 23
        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 23
    public function createMemberModelWithUser($user)
237
    {
238
        $config = [
239 23
            'memberUser' => $user,
240 23
            'organization' => $this,
241 23
            'nickname' => '',
242
        ];
243 23
        if ($user->profile) {
244 23
            $config['nickname'] = $user->profile->nickname;
245
        }
246 23
        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
     * 
268
     * @param Event $event
269
     */
270 24
    public function onAddProfile($event)
271
    {
272 24
        $profile = $event->sender->createProfile($event->data);
273 24
        if (!$profile->save()) {
274
            throw new IntegrityException('Profile Save Failed.');
275
        }
276 24
        return true;
277
    }
278
279
    /**
280
     * 
281
     * @param Event $event
282
     */
283 24
    public function onAssignCreator($event)
284
    {
285 24
        return $event->sender->addCreator($event->data);
286
    }
287
288
    /**
289
     * 
290
     * @param Event $event
291
     */
292 13
    public function onRevokeCreator($event)
293
    {
294 13
        $sender = $event->sender;
295
        /* @var $sender static */
296 13
        $member = $sender->getMemberCreators()->one();
297
        /* @var $member Member */
298 13
        $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator) : (new DepartmentCreator);
299 13
        return $member->revokeRole($role);
300
    }
301
302
    /**
303
     * 
304
     * @param Event $event
305
     */
306 13
    public function onRevokeAdministrators($event)
307
    {
308 13
        $sender = $event->sender;
309
        /* @var $sender static */
310 13
        $members = $sender->getMemberAdministrators()->all();
311
        /* @var $members Member[] */
312 13
        $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationAdmin) : (new DepartmentAdmin);
313 13
        foreach ($members as $member)
314
        {
315
            $member->revokeRole($role);
316
        }
317 13
    }
318
319
    /**
320
     * 
321
     * @param Event $event
322
     */
323 13
    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...
324
    {
325
        
326 13
    }
327
328
    /**
329
     * 
330
     * @return boolean
331
     */
332 2
    public function isOrganization()
333
    {
334 2
        return $this->type == static::TYPE_ORGANIZATION;
335
    }
336
337
    /**
338
     * 
339
     * @return boolean
340
     */
341 2
    public function isDepartment()
342
    {
343 2
        return $this->type == static::TYPE_DEPARTMENT;
344
    }
345
346
    /**
347
     * Check whether the current organization has a member.
348
     * @param User $user
349
     * @return boolean
350
     */
351 1
    public function hasMember($user)
352
    {
353 1
        return !is_null($this->getMember($user));
354
    }
355
356
    /**
357
     * Get creator query.
358
     * @return MemberQuery
359
     */
360 13
    public function getMemberCreators()
361
    {
362 13
        $roleNames = [];
363 13
        $roleNames[] = (new DepartmentCreator)->name;
364 13
        $roleNames[] = (new OrganizationCreator)->name;
365 13
        return $this->getMembers()->andWhere(['role' => $roleNames]);
366
    }
367
368
    /**
369
     * Get administrator query.
370
     * @return MemberQuery
371
     */
372 13
    public function getMemberAdministrators()
373
    {
374 13
        $roleNames = [];
375 13
        $roleNames[] = (new DepartmentAdmin)->name;
376 13
        $roleNames[] = (new OrganizationAdmin)->name;
377 13
        return $this->getMembers()->andWhere(['role' => $roleNames]);
378
    }
379
380
    /**
381
     * 
382
     * @param User $user
383
     * @return boolean
384
     * @throws \Exception
385
     * @throws IntegrityException
386
     */
387 24
    protected function addCreator($user)
388
    {
389 24
        if (!$user) {
390 1
            throw new InvalidParamException('Creator Invalid.');
391
        }
392 23
        $member = $user;
393 23
        $transaction = Yii::$app->db->beginTransaction();
394
        try {
395 23
            if (!$this->addMember($member)) {
396
                throw new IntegrityException('Failed to add member.');
397
            }
398 23
            $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationCreator) : (new DepartmentCreator);
399 23
            $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...
400 23
            if (!$member->save()) {
401
                throw new IntegrityException('Failed to assign creator.');
402
            }
403 23
            $transaction->commit();
404
        } catch (\Exception $ex) {
405
            $transaction->rollBack();
406
            Yii::error($ex->getMessage(), __METHOD__);
407
            throw $ex;
408
        }
409 23
        return true;
410
    }
411
412
    /**
413
     * 
414
     * @param User $user
415
     * @return boolean
416
     * @throws \Exception
417
     * @throws IntegrityException
418
     */
419 1
    public function addAdministrator($user)
420
    {
421 1
        $member = $user;
422 1
        $transaction = Yii::$app->db->beginTransaction();
423
        try {
424 1
            if (!$this->addMember($member)) {
425
                throw new IntegrityException('Failed to add member.');
426
            }
427 1
            $role = $this->type == static::TYPE_ORGANIZATION ? (new OrganizationAdmin) : (new DepartmentAdmin);
428 1
            $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...
429 1
            if (!$member->save()) {
430
                throw new IntegrityException('Failed to assign administrator.');
431
            }
432 1
            $transaction->commit();
433
        } catch (\Exception $ex) {
434
            $transaction->rollBack();
435
            Yii::error($ex->getMessage(), __METHOD__);
436
            throw $ex;
437
        }
438 1
        return true;
439
    }
440
}
441