Passed
Push — master ( 05e7db...c14628 )
by vistart
04:22
created

getAdministratorsAtOrganizations()   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
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
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\organization\exceptions\RevokePreventedException;
16
use rhosocial\organization\queries\MemberQuery;
17
use rhosocial\organization\queries\OrganizationQuery;
18
use rhosocial\organization\rbac\permissions\SetUpOrganization;
19
use rhosocial\organization\rbac\permissions\SetUpDepartment;
20
use rhosocial\organization\rbac\permissions\RevokeOrganization;
21
use rhosocial\organization\rbac\permissions\RevokeDepartment;
22
use rhosocial\organization\rbac\roles\DepartmentAdmin;
23
use rhosocial\organization\rbac\roles\DepartmentCreator;
24
use rhosocial\organization\rbac\roles\OrganizationAdmin;
25
use rhosocial\organization\rbac\roles\OrganizationCreator;
26
use Yii;
27
use yii\base\Event;
28
use yii\base\InvalidConfigException;
29
use yii\base\InvalidParamException;
30
31
/**
32
 * @property string $guidAttribute GUID Attribute.
33
 * @property-read Member[] $ofMembers
34
 * @property-read Organization[] $atOrganizations
35
 * @property-read Organization[] $atOrganizationsOnly
36
 * @property-read Organization[] $atDepartmentsOnly
37
 * @property-read Organization[] $creatorsAtOrganizations
38
 * @property-read Organization[] $administratorsAtOrganizations
39
 *
40
 * @version 1.0
41
 * @author vistart <[email protected]>
42
 */
43
trait UserOrganizationTrait
44
{
45
    public $organizationClass = Organization::class;
46
    public $memberClass = Member::class;
47
    private $noInitOrganization;
48
    private $noInitMember;
49
    public $lastSetUpOrganization;
50
    /**
51
     * @return Organization
52
     */
53
    public function getNoInitOrganization()
54
    {
55
        if (!$this->noInitOrganization) {
56
            $class = $this->organizationClass;
57
            $this->noInitOrganization = $class::buildNoInitModel();
58
        }
59
        return $this->noInitOrganization;
60
    }
61
    /**
62
     * @return Member
63
     */
64 28
    public function getNoInitMember()
65
    {
66 28
        if (!$this->noInitMember) {
67 28
            $class = $this->memberClass;
68 28
            $this->noInitMember = $class::buildNoInitModel();
69
        }
70 28
        return $this->noInitMember;
71
    }
72
73
    /**
74
     * Get member query.
75
     * @return MemberQuery
76
     */
77 28
    public function getOfMembers()
78
    {
79 28
        return $this->hasMany($this->memberClass, [$this->getNoInitMember()->memberAttribute => $this->guidAttribute])->inverseOf('memberUser');
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
80
    }
81
82
    /**
83
     * Get query of member whose role is creator.
84
     * @return MemberQuery
85
     */
86 16
    public function getOfCreators()
87
    {
88 16
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
89
    }
90
91
    /**
92
     * Get query of member whose role is administrator.
93
     * @return MemberQuery
94
     */
95 2
    public function getOfAdministrators()
96
    {
97 2
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
98
    }
99
100
    /**
101
     * Get query of organization of which this user has been a member.
102
     * If you access this method as magic-property `atOrganizations`, you will
103
     * get all organizations the current user has joined in.
104
     * @return OrganizationQuery
105
     */
106 12
    public function getAtOrganizations()
107
    {
108 12
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofMembers');
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
109
    }
110
111
    /**
112
     * 
113
     * @return OrganizationQuery
114
     */
115
    public function getAtOrganizationsOnly()
116
    {
117
        return $this->getAtOrganizations()->andWhere(['type' => Organization::TYPE_ORGANIZATION]);
118
    }
119
120
    /**
121
     * 
122
     * @return OrganizationQuery
123
     */
124
    public function getAtDepartmentsOnly()
125
    {
126
        return $this->getAtOrganizations()->andWhere(['type' => Organization::TYPE_DEPARTMENT]);
127
    }
128
129
    /**
130
     * 
131
     * @return OrganizationQuery
132
     */
133 16
    public function getCreatorsAtOrganizations()
134
    {
135 16
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofCreators');
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
136
    }
137
138
    /**
139
     * 
140
     * @return OrganizationQuery
141
     */
142 2
    public function getAdministratorsAtOrganizations()
143
    {
144 2
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofAdministrators');
0 ignored issues
show
Bug introduced by
It seems like hasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
145
    }
146
147
    /**
148
     * Set up organization.
149
     * @param string $name
150
     * @param string $nickname
151
     * @param integer $gravatar_type
152
     * @param string $gravatar
153
     * @param string $timezone
154
     * @param string $description
155
     * @return boolean Whether indicate the setting-up succeeded or not.
156
     * @throws InvalidParamException
157
     * @throws \Exception
158
     */
159 35
    public function setUpOrganization($name, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
0 ignored issues
show
Unused Code introduced by
The parameter $nickname 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...
Unused Code introduced by
The parameter $gravatar_type 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...
Unused Code introduced by
The parameter $gravatar 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...
Unused Code introduced by
The parameter $timezone 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...
Unused Code introduced by
The parameter $description 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...
160
    {
161 35
        $accessChecker = Yii::$app->authManager;
162 35
        if (!$accessChecker->checkAccess($this, (new SetUpOrganization)->name)) {
163
            throw new InvalidParamException("You do not have permission to set up organization.");
164
        }
165 35
        $transaction = Yii::$app->db->beginTransaction();
166
        try {
167 35
            $models = $this->createOrganization($name, null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '');
168 35
            $this->setUpBaseOrganization($models);
169 34
            $transaction->commit();
170 1
        } catch (\Exception $ex) {
171 1
            $transaction->rollBack();
172 1
            Yii::error($ex->getMessage(), __METHOD__);
173 1
            throw $ex;
174
        }
175 34
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
176 34
        return true;
177
    }
178
179
    /**
180
     * Set up organization.
181
     * @param string $name Department name.
182
     * @param Organization $parent Parent organization or department.
183
     * @param string $nickname
184
     * @param integer $gravatar_type
185
     * @param string $gravatar
186
     * @param string $timezone
187
     * @param string $description
188
     * @return boolean Whether indicate the setting-up succeeded or not.
189
     * @throws InvalidParamException
190
     * @throws \Exception
191
     */
192 8
    public function setUpDepartment($name, $parent, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
0 ignored issues
show
Unused Code introduced by
The parameter $nickname 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...
Unused Code introduced by
The parameter $gravatar_type 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...
Unused Code introduced by
The parameter $gravatar 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...
Unused Code introduced by
The parameter $timezone 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...
Unused Code introduced by
The parameter $description 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...
193
    {
194 8
        if (!($parent instanceof $this->organizationClass)) {
195 1
            throw new InvalidParamException('Invalid Parent Parameter.');
196
        }
197 7
        $accessChecker = Yii::$app->authManager;
198 7
        if (!$accessChecker->checkAccess($this, (new SetUpDepartment)->name, ['organization' => $parent])) {
199 1
            throw new InvalidParamException("You do not have permission to set up department.");
200
        }
201 7
        $transaction = Yii::$app->db->beginTransaction();
202
        try {
203 7
            $models = $this->createDepartment($name, $parent, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '');
204 7
            $this->setUpBaseOrganization($models);
205 6
            $transaction->commit();
206 1
        } catch (\Exception $ex) {
207 1
            $transaction->rollBack();
208 1
            Yii::error($ex->getMessage(), __METHOD__);
209 1
            throw $ex;
210
        }
211 6
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
212 6
        return true;
213
    }
214
215
    /**
216
     * Set up base organization.
217
     * @param Organization $models
218
     * @return boolean
219
     * @throws InvalidConfigException
220
     * @throws \Exception
221
     */
222 35
    protected function setUpBaseOrganization($models)
223
    {
224 35
        $model = null;
225 35
        $associatedModels = [];
226 35
        if (is_array($models)) {
227 2
            if (!array_key_exists(0, $models)) {
228 2
                throw new InvalidConfigException('Invalid Organization Model.');
229
            }
230
            $model = $models[0];
231
            $associatedModels = array_key_exists('associatedModels', $models) ? $models['associatedModels'] : [];
232 34
        } elseif ($models instanceof $this->organizationClass) {
233 34
            $model = $models;
234
        }
235 34
        $result = $model->register($associatedModels);
236 34
        if ($result instanceof \Exception) {
237
            throw $result;
238
        }
239 34
        if ($result !== true) {
240
            throw new \Exception('Failed to set up.');
241
        }
242 34
        return true;
243
    }
244
245
    /**
246
     * Create organization.
247
     * @param string $name
248
     * @param Organization $parent
249
     * @param string $nickname
250
     * @param string $gravatar_type
251
     * @param string $gravatar
252
     * @param string $timezone
253
     * @param string $description
254
     * @return Organization
255
     */
256 34
    public function createOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
257
    {
258 34
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description);
259
    }
260
261
    /**
262
     * Create department.
263
     * @param string $name
264
     * @param Organization $parent
265
     * @param string $nickname
266
     * @param string $gravatar_type
267
     * @param string $gravatar
268
     * @param string $timezone
269
     * @param string $description
270
     * @return Organization
271
     */
272 6
    public function createDepartment($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
273
    {
274 6
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description, Organization::TYPE_DEPARTMENT);
275
    }
276
277
    /**
278
     * Create Base Organization.
279
     * @param string $name
280
     * @param Organization $parent
281
     * @param string $nickname
282
     * @param integer $gravatar_type
283
     * @param string $gravatar
284
     * @param string $timezone
285
     * @param string $description
286
     * @param integer $type
287
     * @return Organization
288
     * @throws InvalidParamException throw if setting parent failed. Possible reasons include:
289
     * - The parent is itself.
290
     * - The parent has already been its ancestor.
291
     * - The current organization has reached the limit of ancestors.
292
     */
293 34
    protected function createBaseOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '', $type = Organization::TYPE_ORGANIZATION)
294
    {
295 34
        $class = $this->organizationClass;
296
        $profileConfig = [
297 34
            'name' => $name,
298 34
            'nickname' => $nickname,
299 34
            'gravatar_type' => $gravatar_type,
300 34
            'gravatar' => $gravatar,
301 34
            'timezone' => $timezone,
302 34
            'description' => $description,
303
        ];
304 34
        $organization = new $class(['type' => $type, 'creatorModel' => $this, 'profileConfig' => $profileConfig]);
305 34
        if (empty($parent)) {
306 34
            $organization->setNullParent();
307 6
        } elseif ($organization->setParent($parent) === false) {
308
            throw new InvalidParamException("Failed to set parent.");
309
        }
310 34
        return $organization;
311
    }
312
313
    /**
314
     * Revoke organization or department.
315
     * @param Organization|string|integer $organization Organization or it's ID or GUID.
316
     * @param boolean $revokeIfHasChildren True represents revoking organization if there are subordinates.
317
     * @return boolean True if revocation is successful.
318
     * @throws InvalidParamException throws if organization is invalid.
319
     * @throws \Exception
320
     * @throws RevokePreventedException throws if $revokeIfHasChildren is false, at the
321
     * same time the current organization or department has subordinates.
322
     * @throws @var:$organization@mtd:deregister
323
     */
324 9
    public function revokeOrganization($organization, $revokeIfHasChildren = true)
325
    {
326 9
        if (!($organization instanceof $this->organizationClass))
327
        {
328 2
            $class = $this->organizationClass;
329 2
            if (is_numeric($organization)) {
330 1
                $organization = $class::find()->id($organization)->one();
331 1
            } elseif (is_string($organization)) {
332 1
                $organization = $class::find()->guid($organization)->one();
333
            }
334
        }
335 9
        if (!($organization instanceof $this->organizationClass)) {
336
            throw new InvalidParamException('Invalid Organization.');
337
        }
338 9
        if (!Yii::$app->authManager->checkAccess(
339
                $this,
340 9
                $organization->type == Organization::TYPE_ORGANIZATION ? (new RevokeOrganization)->name : (new RevokeDepartment)->name,
341 9
                ['organization' => $organization])) {
342 1
            throw new InvalidParamException("You do not have permission to revoke it.");
343
        }
344 8
        $transaction = Yii::$app->db->beginTransaction();
345
        try {
346 8
            if (!$revokeIfHasChildren && ((int)($organization->getChildren()->count())) > 0) {
347
                $type = $organization->isOrganization() ? "organization" : "department";
348
                throw new RevokePreventedException("The $type has children. Revoking prevented.");
349
            }
350 8
            $result = $organization->deregister();
351 8
            if ($result instanceof \Exception){
352
                throw $result;
353
            }
354 8
            if ($result !== true) {
355
                throw new InvalidParamException("Failed to revoke.");
356
            }
357 8
            $transaction->commit();
358
        } catch (\Exception $ex) {
359
            $transaction->rollBack();
360
            Yii::error($ex->getMessage(), __METHOD__);
361
            throw $ex;
362
        }
363 8
        return true;
364
    }
365
366
    /**
367
     * Check whether current user is organization or department creator.
368
     * @param Organization $organization
369
     * @return boolean True if current is organization or department creator.
370
     */
371 14
    public function isOrganizationCreator($organization)
372
    {
373 14
        $member = $organization->getMember($this);
374 14
        if (!$member) {
375 2
            return false;
376
        }
377 14
        return $member->isCreator();
378
    }
379
380
    /**
381
     * Check whether current user is organization or department administrator.
382
     * @param Organization $organization
383
     * @return boolean True if current is organization or department administrator.
384
     */
385 5
    public function isOrganizationAdministrator($organization)
386
    {
387 5
        $member = $organization->getMember($this);
388 5
        if (!$member) {
389 3
            return false;
390
        }
391 5
        return $member->isAdministrator();
392
    }
393
394
    /**
395
     * Attach events associated with organization.
396
     */
397 36
    public function initOrganizationEvents()
398
    {
399 36
        $this->on(static::EVENT_BEFORE_DELETE, [$this, "onRevokeOrganizationsByCreator"]);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
400 36
    }
401
402
    /**
403
     * Revoke Organization Event.
404
     * It should be triggered when deleting (not deregistering).
405
     * @param Event $event
406
     */
407 15
    public function onRevokeOrganizationsByCreator($event)
408
    {
409 15
        $sender = $event->sender;
410
        /* @var $sender static */
411 15
        $organizations = $this->creatorsAtOrganizations;
412 15
        foreach ($organizations as $org)
413
        {
414 2
            $sender->revokeOrganization($org);
415
        }
416 15
    }
417
}
418