Code

< 40 %
40-60 %
> 60 %
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\queries\BaseBlameableQuery;
16
use rhosocial\organization\exceptions\RevokePreventedException;
17
use rhosocial\organization\queries\MemberQuery;
18
use rhosocial\organization\queries\OrganizationQuery;
19
use rhosocial\organization\rbac\permissions\SetUpOrganization;
20
use rhosocial\organization\rbac\permissions\SetUpDepartment;
21
use rhosocial\organization\rbac\permissions\RevokeOrganization;
22
use rhosocial\organization\rbac\permissions\RevokeDepartment;
23
use rhosocial\organization\rbac\roles\DepartmentAdmin;
24
use rhosocial\organization\rbac\roles\DepartmentCreator;
25
use rhosocial\organization\rbac\roles\OrganizationAdmin;
26
use rhosocial\organization\rbac\roles\OrganizationCreator;
27
use Yii;
28
use yii\base\Event;
29
use yii\base\InvalidConfigException;
30
use yii\base\InvalidParamException;
31
32
/**
33
 * @property string $guidAttribute GUID Attribute.
34
 * @property-read Member[] $ofMembers
35
 * @property-read Member[] $ofCreators
36
 * @property-read Member[] $ofAdministrators
37
 * @property-read Organization[] $atOrganizations
38
 * @property-read Organization[] $atOrganizationsOnly
39
 * @property-read Organization[] $atDepartmentsOnly
40
 * @property-read Organization[] $creatorsAtOrganizations
41
 * @property-read Organization[] $creatorsAtOrganizationsOnly
42
 * @property-read Organization[] $administratorsAtOrganizations
43
 * @property-read Organization[] $administratorsAtOrganizationsOnly
44
 * @property-read OrganizationLimit $organizationLimit
45
 *
46
 * @version 1.0
47
 * @author vistart <[email protected]>
48
 */
49
trait UserOrganizationTrait
50
{
51
    /**
52
     * @var string The organization class.
53
     * Note: Please assign it with your own Organization class.
54
     */
55
    public $organizationClass = Organization::class;
56
57
    /**
58
     * @var string The organization limit class.
59
     * Note: Please assign it with your own OrganizationLimit class.
60
     */
61
    public $organizationLimitClass = OrganizationLimit::class;
62
    private $noInitOrganizationLimit;
63
    private $noInitOrganization;
64
    public $lastSetUpOrganization;
65
66
    /**
67
     * @return OrganizationLimit
68
     */
69 51
    public function getNoInitOrganizationLimit()
70
    {
71 51
        if (!$this->noInitOrganizationLimit) {
72 51
            $class = $this->organizationLimitClass;
73 51
            $this->noInitOrganizationLimit = $class::buildNoInitModel();
74
        }
75 51
        return $this->noInitOrganizationLimit;
76
    }
77
    /**
78
     * @return Organization
79
     */
80 51
    public function getNoInitOrganization()
81
    {
82 51
        if (!$this->noInitOrganization) {
83 51
            $class = $this->organizationClass;
84 51
            $this->noInitOrganization = $class::buildNoInitModel();
85
        }
86 51
        return $this->noInitOrganization;
87
    }
88
    /**
89
     * @return Member
90
     */
91 51
    public function getNoInitMember()
92
    {
93 51
        return $this->getNoInitOrganization()->getNoInitMember();
94
    }
95
96
    /**
97
     * Get member query.
98
     * @return MemberQuery
99
     */
100 51
    public function getOfMembers()
101
    {
102 51
        return $this->hasMany(get_class($this->getNoInitMember()), [$this->getNoInitMember()->memberAttribute => $this->guidAttribute])->inverseOf('memberUser');
103
    }
104
105
    /**
106
     * Get query of member whose role is creator.
107
     * @return MemberQuery
108
     */
109 51
    public function getOfCreators()
110
    {
111 51
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
112
    }
113
114
    /**
115
     * Get query of member whose role is administrator.
116
     * @return MemberQuery
117
     */
118 4
    public function getOfAdministrators()
119
    {
120 4
        return $this->getOfMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
121
    }
122
123
    /**
124
     * Get query of organization of which this user has been a member.
125
     * If you access this method as magic-property `atOrganizations`, you will
126
     * get all organizations the current user has joined in.
127
     * @return OrganizationQuery
128
     */
129 50
    public function getAtOrganizations()
130
    {
131 50
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofMembers');
132
    }
133
134
    /**
135
     * 
136
     * @return OrganizationQuery
137
     */
138 2
    public function getAtOrganizationsOnly()
139
    {
140 2
        return $this->getAtOrganizations()->andWhere(['type' => Organization::TYPE_ORGANIZATION]);
141
    }
142
143
    /**
144
     * 
145
     * @return OrganizationQuery
146
     */
147 1
    public function getAtDepartmentsOnly()
148
    {
149 1
        return $this->getAtOrganizations()->andWhere(['type' => Organization::TYPE_DEPARTMENT]);
150
    }
151
152
    /**
153
     * 
154
     * @return OrganizationQuery
155
     */
156 51
    public function getCreatorsAtOrganizations()
157
    {
158 51
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofCreators');
159
    }
160
161
    /**
162
     *
163
     * @return OrganizationQuery
164
     */
165 51
    public function getCreatorsAtOrganizationsOnly()
166
    {
167 51
        return $this->getCreatorsAtOrganizations()->andWhere(['type' => Organization::TYPE_ORGANIZATION]);
168
    }
169
170
    /**
171
     * 
172
     * @return OrganizationQuery
173
     */
174 4
    public function getAdministratorsAtOrganizations()
175
    {
176 4
        return $this->hasMany($this->organizationClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->via('ofAdministrators');
177
    }
178
179
    /**
180
     * @return OrganizationQuery
181
     */
182 2
    public function getAdministratorsAtOrganizationsOnly()
183
    {
184 2
        return $this->getAdministratorsAtOrganizations()->andWhere(['type' => Organization::TYPE_ORGANIZATION]);
185
    }
186
187
    /**
188
     * Get Organization Limit Query.
189
     * @return BaseBlameableQuery
190
     */
191 51
    public function getOrganizationLimit()
192
    {
193 51
        if (empty($this->organizationLimitClass)) {
194
            return null;
195
        }
196 51
        return $this->hasOne($this->organizationLimitClass, [$this->getNoInitOrganizationLimit()->createdByAttribute => $this->guidAttribute]);
197
    }
198
199
    /**
200
     * Set up organization.
201
     * @param string $name
202
     * @param string $nickname
203
     * @param integer $gravatar_type
204
     * @param string $gravatar
205
     * @param string $timezone
206
     * @param string $description
207
     * @return boolean Whether indicate the setting-up succeeded or not.
208
     * @throws InvalidParamException
209
     * @throws \Exception
210
     */
211 51
    public function setUpOrganization($name, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
212
    {
213 51
        $accessChecker = Yii::$app->authManager;
214 51
        if (!$accessChecker->checkAccess($this, (new SetUpOrganization)->name)) {
215 1
            throw new InvalidParamException("You do not have permission to set up organization.");
216
        }
217 51
        $transaction = Yii::$app->db->beginTransaction();
218
        try {
219 51
            $models = $this->createOrganization($name, null, $nickname, $gravatar_type, $gravatar, $timezone, $description);
220 51
            $this->setUpBaseOrganization($models);
221 50
            $transaction->commit();
222 1
        } catch (\Exception $ex) {
223 1
            $transaction->rollBack();
224 1
            Yii::error($ex->getMessage(), __METHOD__);
225 1
            throw $ex;
226
        }
227 50
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
228 50
        return true;
229
    }
230
231
    /**
232
     * Set up organization.
233
     * @param string $name Department name.
234
     * @param Organization $parent Parent organization or department.
235
     * @param string $nickname
236
     * @param integer $gravatar_type
237
     * @param string $gravatar
238
     * @param string $timezone
239
     * @param string $description
240
     * @return boolean Whether indicate the setting-up succeeded or not.
241
     * @throws InvalidParamException
242
     * @throws \Exception
243
     */
244 20
    public function setUpDepartment($name, $parent, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
245
    {
246 20
        if (!($parent instanceof $this->organizationClass)) {
247 1
            throw new InvalidParamException('Invalid Parent Parameter.');
248
        }
249 19
        $accessChecker = Yii::$app->authManager;
250 19
        if (!$accessChecker->checkAccess($this, (new SetUpDepartment)->name, ['organization' => $parent])) {
251 2
            throw new InvalidParamException("You do not have permission to set up department.");
252
        }
253 19
        $transaction = Yii::$app->db->beginTransaction();
254
        try {
255 19
            $models = $this->createDepartment($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description);
256 19
            $this->setUpBaseOrganization($models);
257 18
            $transaction->commit();
258 1
        } catch (\Exception $ex) {
259 1
            $transaction->rollBack();
260 1
            Yii::error($ex->getMessage(), __METHOD__);
261 1
            throw $ex;
262
        }
263 18
        $this->lastSetUpOrganization = is_array($models) ? $models[0] : $models;
264 18
        return true;
265
    }
266
267
    /**
268
     * Set up base organization.
269
     * @param Organization $models
270
     * @return boolean
271
     * @throws InvalidConfigException
272
     * @throws \Exception
273
     */
274 51
    protected function setUpBaseOrganization($models)
275
    {
276 51
        $model = null;
277 51
        $associatedModels = [];
278 51
        if (is_array($models)) {
279 2
            if (!array_key_exists(0, $models)) {
280 2
                throw new InvalidConfigException('Invalid Organization Model.');
281
            }
282
            $model = $models[0];
283
            $associatedModels = array_key_exists('associatedModels', $models) ? $models['associatedModels'] : [];
284 50
        } elseif ($models instanceof $this->organizationClass) {
285 50
            $model = $models;
286
        }
287 50
        $result = $model->register($associatedModels);
288 50
        if ($result instanceof \Exception) {
289
            throw $result;
290
        }
291 50
        if ($result !== true) {
292
            throw new \Exception('Failed to set up.');
293
        }
294 50
        return true;
295
    }
296
297
    /**
298
     * Create organization.
299
     * @param string $name
300
     * @param Organization $parent
301
     * @param string $nickname
302
     * @param string $gravatar_type
303
     * @param string $gravatar
304
     * @param string $timezone
305
     * @param string $description
306
     * @return Organization
307
     */
308 50
    public function createOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
309
    {
310 50
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description);
311
    }
312
313
    /**
314
     * Create department.
315
     * @param string $name
316
     * @param Organization $parent
317
     * @param string $nickname
318
     * @param string $gravatar_type
319
     * @param string $gravatar
320
     * @param string $timezone
321
     * @param string $description
322
     * @return Organization
323
     */
324 18
    public function createDepartment($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '')
325
    {
326 18
        return $this->createBaseOrganization($name, $parent, $nickname, $gravatar_type, $gravatar, $timezone, $description, Organization::TYPE_DEPARTMENT);
327
    }
328
329
    /**
330
     * Create Base Organization.
331
     * @param string $name
332
     * @param Organization $parent
333
     * @param string $nickname
334
     * @param integer $gravatar_type
335
     * @param string $gravatar
336
     * @param string $timezone
337
     * @param string $description
338
     * @param integer $type
339
     * @return Organization
340
     * @throws InvalidParamException throw if setting parent failed. Possible reasons include:
341
     * - The parent is itself.
342
     * - The parent has already been its ancestor.
343
     * - The current organization has reached the limit of ancestors.
344
     */
345 50
    protected function createBaseOrganization($name, $parent = null, $nickname = '', $gravatar_type = 0, $gravatar = '', $timezone = 'UTC', $description = '', $type = Organization::TYPE_ORGANIZATION)
346
    {
347 50
        $class = $this->organizationClass;
348
        $profileConfig = [
349 50
            'name' => $name,
350 50
            'nickname' => $nickname,
351 50
            'gravatar_type' => $gravatar_type,
352 50
            'gravatar' => $gravatar,
353 50
            'timezone' => $timezone,
354 50
            'description' => $description,
355
        ];
356 50
        $organization = new $class(['type' => $type, 'creatorModel' => $this, 'profileConfig' => $profileConfig]);
357 50
        if (empty($parent)) {
358 50
            $organization->setNullParent();
359 18
        } elseif ($organization->setParent($parent) === false) {
360
            throw new InvalidParamException("Failed to set parent.");
361
        }
362 50
        return $organization;
363
    }
364
365
    /**
366
     * Revoke organization or department.
367
     * @param Organization|string|integer $organization Organization or it's ID or GUID.
368
     * @param boolean $revokeIfHasChildren True represents revoking organization if there are subordinates.
369
     * @return boolean True if revocation is successful.
370
     * @throws InvalidParamException throws if organization is invalid.
371
     * @throws \Exception
372
     * @throws RevokePreventedException throws if $revokeIfHasChildren is false, at the
373
     * same time the current organization or department has subordinates.
374
     * @throws @var:$organization@mtd:deregister
375
     */
376 14
    public function revokeOrganization($organization, $revokeIfHasChildren = true)
377
    {
378 14
        if (!($organization instanceof $this->organizationClass))
379
        {
380 2
            $class = $this->organizationClass;
381 2
            $organization = $class::find()->guidOrId($organization)->one();
382
        }
383 14
        if (!($organization instanceof $this->organizationClass)) {
384
            throw new InvalidParamException('Invalid Organization.');
385
        }
386 14
        if (!Yii::$app->authManager->checkAccess(
387
                $this,
388 14
                $organization->type == Organization::TYPE_ORGANIZATION ? (new RevokeOrganization)->name : (new RevokeDepartment)->name,
389 14
                ['organization' => $organization])) {
390 1
            throw new InvalidParamException("You do not have permission to revoke it.");
391
        }
392 13
        $transaction = Yii::$app->db->beginTransaction();
393
        try {
394 13
            if (!$revokeIfHasChildren && ((int)($organization->getChildren()->count())) > 0) {
395
                $type = $organization->isOrganization() ? "organization" : "department";
396
                throw new RevokePreventedException("The $type has children. Revoking prevented.");
397
            }
398 13
            $result = $organization->deregister();
399 13
            if ($result instanceof \Exception){
400
                throw $result;
401
            }
402 13
            if ($result !== true) {
403
                throw new InvalidParamException("Failed to revoke.");
404
            }
405 13
            $transaction->commit();
406
        } catch (\Exception $ex) {
407
            $transaction->rollBack();
408
            Yii::error($ex->getMessage(), __METHOD__);
409
            throw $ex;
410
        }
411 13
        return true;
412
    }
413
414
    /**
415
     * Check whether current user is organization or department creator.
416
     * @param Organization $organization
417
     * @return boolean True if current is organization or department creator.
418
     */
419 29
    public function isOrganizationCreator($organization)
420
    {
421 29
        $member = $organization->getMember($this);
422 29
        if (!$member) {
423 2
            return false;
424
        }
425 29
        return $member->isCreator();
426
    }
427
428
    /**
429
     * Check whether current user is organization or department administrator.
430
     * @param Organization $organization
431
     * @return boolean True if current is organization or department administrator.
432
     */
433 14
    public function isOrganizationAdministrator($organization)
434
    {
435 14
        $member = $organization->getMember($this);
436 14
        if (!$member) {
437 3
            return false;
438
        }
439 14
        return $member->isAdministrator();
440
    }
441
442
    /**
443
     * Attach events associated with organization.
444
     */
445 52
    public function initOrganizationEvents()
446
    {
447 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, "onRevokeOrganizationsByCreator"]);
448 52
    }
449
450
    /**
451
     * Revoke Organization Event.
452
     * It should be triggered when deleting (not deregistering).
453
     * @param Event $event
454
     */
455 22
    public function onRevokeOrganizationsByCreator($event)
456
    {
457 22
        $sender = $event->sender;
458
        /* @var $sender static */
459 22
        $organizations = $this->creatorsAtOrganizations;
460 22
        foreach ($organizations as $org)
461
        {
462 4
            $sender->revokeOrganization($org);
463
        }
464 22
    }
465
466
    /**
467
     * Check whether the current user has reached the upper limit of organizations.
468
     * @return boolean the upper limit of organizations which current could be set up.
469
     */
470 51
    public function hasReachedOrganizationLimit()
471
    {
472 51
        $remaining = $this->getRemainingOrganizationPlaces();
473 51
        if ($remaining === false) {
474
            return false;
475
        }
476 51
        return $remaining <= 0;
477
    }
478
479
    /**
480
     * Get the remaining places of organizations.
481
     * @return bool|int False if no limit.
482
     */
483 51
    public function getRemainingOrganizationPlaces()
484
    {
485 51
        $class = $this->organizationLimitClass;
486 51
        if (empty($class)) {
487
            return false;
488
        }
489 51
        $limit = $class::getLimit($this);
490 51
        if ($limit === false) {
491
            return false;
492
        }
493 51
        $count = (int)$this->getCreatorsAtOrganizationsOnly()->count();
494 51
        return $limit - $count;
495
    }
496
}
497