Passed
Push — master ( 886b4c...f3a025 )
by vistart
04:20
created

getCreatorsAtOrganizations()   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[] $creatorsAtOrganizations
36
 * @property-read Organization[] $administratorsAtOrganizations
37
 *
38
 * @version 1.0
39
 * @author vistart <[email protected]>
40
 */
41
trait UserOrganizationTrait
42
{
43
    public $organizationClass = Organization::class;
44
    public $memberClass = Member::class;
45
    private $noInitOrganization;
46
    private $noInitMember;
47
    public $lastSetUpOrganization;
48
    /**
49
     * @return Organization
50
     */
51
    protected function getNoInitOrganization()
52
    {
53
        if (!$this->noInitOrganization) {
54
            $class = $this->organizationClass;
55
            $this->noInitOrganization = $class::buildNoInitModel();
56
        }
57
        return $this->noInitOrganization;
58
    }
59
    /**
60
     * @return Member
61
     */
62 28
    protected function getNoInitMember()
63
    {
64 28
        if (!$this->noInitMember) {
65 28
            $class = $this->memberClass;
66 28
            $this->noInitMember = $class::buildNoInitModel();
67
        }
68 28
        return $this->noInitMember;
69
    }
70
71
    /**
72
     * Get member query.
73
     * @return MemberQuery
74
     */
75 28
    public function getOfMembers()
76
    {
77 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...
78
    }
79
80
    /**
81
     * Get query of member whose role is creator.
82
     * @return MemberQuery
83
     */
84 16
    public function getOfCreators()
85
    {
86 16
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
87
    }
88
89
    /**
90
     * Get query of member whose role is administrator.
91
     * @return MemberQuery
92
     */
93 2
    public function getOfAdministrators()
94
    {
95 2
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
96
    }
97
98
    /**
99
     * Get query of organization of which this user has been a member.
100
     * If you access this method as magic-property `atOrganizations`, you will
101
     * get all organizations the current user has joined in.
102
     * @return OrganizationQuery
103
     */
104 12
    public function getAtOrganizations()
105
    {
106 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...
107
    }
108
109
    /**
110
     * 
111
     * @return OrganizationQuery
112
     */
113 16
    public function getCreatorsAtOrganizations()
114
    {
115 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...
116
    }
117
118
    /**
119
     * 
120
     * @return OrganizationQuery
121
     */
122 2
    public function getAdministratorsAtOrganizations()
123
    {
124 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...
125
    }
126
127
    /**
128
     * Set up organization.
129
     * @param string $name
130
     * @param string $nickname
131
     * @param integer $gravatar_type
132
     * @param string $gravatar
133
     * @param string $timezone
134
     * @param string $description
135
     * @return boolean Whether indicate the setting-up succeeded or not.
136
     * @throws InvalidParamException
137
     * @throws \Exception
138
     */
139 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...
140
    {
141 35
        $accessChecker = Yii::$app->authManager;
142 35
        if (!$accessChecker->checkAccess($this, (new SetUpOrganization)->name)) {
143
            throw new InvalidParamException("You do not have permission to set up organization.");
144
        }
145 35
        $transaction = Yii::$app->db->beginTransaction();
146
        try {
147 35
            $models = $this->createOrganization($name, null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '');
148 35
            $this->setUpBaseOrganization($models);
149 34
            $transaction->commit();
150 1
        } catch (\Exception $ex) {
151 1
            $transaction->rollBack();
152 1
            Yii::error($ex->getMessage(), __METHOD__);
153 1
            throw $ex;
154
        }
155 34
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
156 34
        return true;
157
    }
158
159
    /**
160
     * Set up organization.
161
     * @param string $name Department name.
162
     * @param Organization $parent Parent organization or department.
163
     * @param string $nickname
164
     * @param integer $gravatar_type
165
     * @param string $gravatar
166
     * @param string $timezone
167
     * @param string $description
168
     * @return boolean Whether indicate the setting-up succeeded or not.
169
     * @throws InvalidParamException
170
     * @throws \Exception
171
     */
172 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...
173
    {
174 8
        if (!($parent instanceof $this->organizationClass)) {
175 1
            throw new InvalidParamException('Invalid Parent Parameter.');
176
        }
177 7
        $accessChecker = Yii::$app->authManager;
178 7
        if (!$accessChecker->checkAccess($this, (new SetUpDepartment)->name, ['organization' => $parent])) {
179 1
            throw new InvalidParamException("You do not have permission to set up department.");
180
        }
181 7
        $transaction = Yii::$app->db->beginTransaction();
182
        try {
183 7
            $models = $this->createDepartment($name, $parent, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '');
184 7
            $this->setUpBaseOrganization($models);
185 6
            $transaction->commit();
186 1
        } catch (\Exception $ex) {
187 1
            $transaction->rollBack();
188 1
            Yii::error($ex->getMessage(), __METHOD__);
189 1
            throw $ex;
190
        }
191 6
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
192 6
        return true;
193
    }
194
195
    /**
196
     * Set up base organization.
197
     * @param Organization $models
198
     * @return boolean
199
     * @throws InvalidConfigException
200
     * @throws \Exception
201
     */
202 35
    protected function setUpBaseOrganization($models)
203
    {
204 35
        $model = null;
205 35
        $associatedModels = [];
206 35
        if (is_array($models)) {
207 2
            if (!array_key_exists(0, $models)) {
208 2
                throw new InvalidConfigException('Invalid Organization Model.');
209
            }
210
            $model = $models[0];
211
            $associatedModels = array_key_exists('associatedModels', $models) ? $models['associatedModels'] : [];
212 34
        } elseif ($models instanceof $this->organizationClass) {
213 34
            $model = $models;
214
        }
215 34
        $result = $model->register($associatedModels);
216 34
        if ($result instanceof \Exception) {
217
            throw $result;
218
        }
219 34
        if ($result !== true) {
220
            throw new \Exception('Failed to set up.');
221
        }
222 34
        return true;
223
    }
224
225
    /**
226
     * Create organization.
227
     * @param string $name
228
     * @param Organization $parent
229
     * @param string $nickname
230
     * @param string $gravatar_type
231
     * @param string $gravatar
232
     * @param string $timezone
233
     * @param string $description
234
     * @return Organization
235
     */
236 34
    public function createOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
237
    {
238 34
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description);
239
    }
240
241
    /**
242
     * Create department.
243
     * @param string $name
244
     * @param Organization $parent
245
     * @param string $nickname
246
     * @param string $gravatar_type
247
     * @param string $gravatar
248
     * @param string $timezone
249
     * @param string $description
250
     * @return Organization
251
     */
252 6
    public function createDepartment($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
253
    {
254 6
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description, Organization::TYPE_DEPARTMENT);
255
    }
256
257
    /**
258
     * Create Base Organization.
259
     * @param string $name
260
     * @param Organization $parent
261
     * @param string $nickname
262
     * @param integer $gravatar_type
263
     * @param string $gravatar
264
     * @param string $timezone
265
     * @param string $description
266
     * @param integer $type
267
     * @return Organization
268
     * @throws InvalidParamException throw if setting parent failed. Possible reasons include:
269
     * - The parent is itself.
270
     * - The parent has already been its ancestor.
271
     * - The current organization has reached the limit of ancestors.
272
     */
273 34
    protected function createBaseOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '', $type = Organization::TYPE_ORGANIZATION)
274
    {
275 34
        $class = $this->organizationClass;
276
        $profileConfig = [
277 34
            'name' => $name,
278 34
            'nickname' => $nickname,
279 34
            'gravatar_type' => $gravatar_type,
280 34
            'gravatar' => $gravatar,
281 34
            'timezone' => $timezone,
282 34
            'description' => $description,
283
        ];
284 34
        $organization = new $class(['type' => $type, 'creatorModel' => $this, 'profileConfig' => $profileConfig]);
285 34
        if (empty($parent)) {
286 34
            $organization->setNullParent();
287 6
        } elseif ($organization->setParent($parent) === false) {
288
            throw new InvalidParamException("Failed to set parent.");
289
        }
290 34
        return $organization;
291
    }
292
293
    /**
294
     * Revoke organization or department.
295
     * @param Organization|string|integer $organization Organization or it's ID or GUID.
296
     * @param boolean $revokeIfHasChildren True represents revoking organization if there are subordinates.
297
     * @return boolean True if revocation is successful.
298
     * @throws InvalidParamException throws if organization is invalid.
299
     * @throws \Exception
300
     * @throws RevokePreventedException throws if $revokeIfHasChildren is false, at the
301
     * same time the current organization or department has subordinates.
302
     * @throws @var:$organization@mtd:deregister
303
     */
304 9
    public function revokeOrganization($organization, $revokeIfHasChildren = true)
305
    {
306 9
        if (!($organization instanceof $this->organizationClass))
307
        {
308 2
            $class = $this->organizationClass;
309 2
            if (is_numeric($organization)) {
310 1
                $organization = $class::find()->id($organization)->one();
311 1
            } elseif (is_string($organization)) {
312 1
                $organization = $class::find()->guid($organization)->one();
313
            }
314
        }
315 9
        if (!($organization instanceof $this->organizationClass)) {
316
            throw new InvalidParamException('Invalid Organization.');
317
        }
318 9
        if (!Yii::$app->authManager->checkAccess(
319
                $this,
320 9
                $organization->type == Organization::TYPE_ORGANIZATION ? (new RevokeOrganization)->name : (new RevokeDepartment)->name,
321 9
                ['organization' => $organization])) {
322 1
            throw new InvalidParamException("You do not have permission to revoke it.");
323
        }
324 8
        $transaction = Yii::$app->db->beginTransaction();
325
        try {
326 8
            if (!$revokeIfHasChildren && ((int)($organization->getChildren()->count())) > 0) {
327
                $type = $organization->isOrganization() ? "organization" : "department";
328
                throw new RevokePreventedException("The $type has children. Revoking prevented.");
329
            }
330 8
            $result = $organization->deregister();
331 8
            if ($result instanceof \Exception){
332
                throw $result;
333
            }
334 8
            if ($result !== true) {
335
                throw new InvalidParamException("Failed to revoke.");
336
            }
337 8
            $transaction->commit();
338
        } catch (\Exception $ex) {
339
            $transaction->rollBack();
340
            Yii::error($ex->getMessage(), __METHOD__);
341
            throw $ex;
342
        }
343 8
        return true;
344
    }
345
346
    /**
347
     * Check whether current user is organization or department creator.
348
     * @param Organization $organization
349
     * @return boolean True if current is organization or department creator.
350
     */
351 14
    public function isOrganizationCreator($organization)
352
    {
353 14
        $member = $organization->getMember($this);
354 14
        if (!$member) {
355 2
            return false;
356
        }
357 14
        return $member->isCreator();
358
    }
359
360
    /**
361
     * Check whether current user is organization or department administrator.
362
     * @param Organization $organization
363
     * @return boolean True if current is organization or department administrator.
364
     */
365 5
    public function isOrganizationAdministrator($organization)
366
    {
367 5
        $member = $organization->getMember($this);
368 5
        if (!$member) {
369 3
            return false;
370
        }
371 5
        return $member->isAdministrator();
372
    }
373
374
    /**
375
     * Attach events associated with organization.
376
     */
377 36
    public function initOrganizationEvents()
378
    {
379 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...
380 36
    }
381
382
    /**
383
     * Revoke Organization Event.
384
     * It should be triggered when deleting (not deregistering).
385
     * @param Event $event
386
     */
387 15
    public function onRevokeOrganizationsByCreator($event)
388
    {
389 15
        $sender = $event->sender;
390
        /* @var $sender static */
391 15
        $organizations = $this->creatorsAtOrganizations;
392 15
        foreach ($organizations as $org)
393
        {
394 2
            $sender->revokeOrganization($org);
395
        }
396 15
    }
397
}
398