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\traits\SelfBlameableTrait;
16
use rhosocial\base\models\queries\BaseBlameableQuery;
17
use rhosocial\base\models\queries\BaseUserQuery;
18
use rhosocial\organization\exceptions\DisallowMemberJoinOtherException;
19
use rhosocial\organization\exceptions\ExcludeOtherMembersException;
20
use rhosocial\organization\exceptions\OnlyAcceptCurrentOrgMemberException;
21
use rhosocial\organization\exceptions\OnlyAcceptSuperiorOrgMemberException;
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 rhosocial\organization\queries\MemberQuery;
27
use rhosocial\organization\queries\OrganizationQuery;
28
use rhosocial\user\User;
29
use Yii;
30
use yii\base\Event;
31
use yii\base\InvalidParamException;
32
use yii\db\IntegrityException;
33
34
/**
35
 * Organization.
36
 * This class is used to describe an organization or department, depending on the type property.
37
 * Organization or department should be created by the user, it is best not to directly implement their own such.
38
 *
39
 * In general, the organization needs to have `setUpOrganization` permission, and the user does not have this permission
40
 * by default. You need to give this permission to the user who created the organization in advance.
41
 * Department, affiliated with the organization or other department, also need the appropriate permission to set up.
42
 *
43
 * While this can work independently, we still strongly recommend that you declare the Organization class yourself and
44
 * inherit this.
45
 * Then you need to specify the Profile and Member class yourself, like following:
46
```php
47
class Organization extends \rhosocial\organization\Organization
48
{
49
    public $profileClass = Profile::class;
50
    public $memberClass = Member::class;
51
}
52
```
53
 * If you need to limit the number of subordinates, the number of members, you need to specify the appropriate class
54
 * name.
55
 * If there is no limit, you need to set it to false manually.
56
 *
57
 * @method Member createMember(array $config) Create member who is subordinate to this.
58
 * @property int $type Whether indicate this instance is an organization or a department.
59
 *
60
 * @property bool $isExcludeOtherMembers Determine whether the other organization and its subordinate departments
61
 * members could join in the current organization and its subordinate departments. (Only fit for Organization)
62
 * @property bool $isDisallowMemberJoinOther Determine whether the current organization and its subordinate
63
 * departments members could join in the other organization and its subordinate departments. (Only fit for Organization)
64
 * @property bool $isOnlyAcceptCurrentOrgMember Determine whether the current department only accept the member of
65
 * the top level organization. (Only fit for Department)
66
 * @property bool $isOnlyAcceptSuperiorOrgMember Determine whether the current department only accept the member of
67
 * the superior organization or department. (Only fit for Department)
68
 * @property string $joinPassword
69
 * @property string $joinIpAddress
70
 * @property string $joinEntranceUrl
71
 * @property bool $exitAllowWithdrawActively
72
 *
73
 * @property-read Member[] $members Get all member models of this organization/department.
74
 * @property-read User[] $memberUsers Get all members of this organization/department.
75
 * @property-read User $creator Get creator of this organization/department.
76
 * @property-read User[] $administrators Get administrators of this organization/department.
77
 * @property-read SubordinateLimit subordinateLimit
78
 * @property-read MemberLimit memberLimit
79
 * @property-read static|null $topOrganization The top level organization of current organization or departments.
80
 * @property-read Profile $profile Get profile model. Friendly to IDE.
81
 * @property-read OrganizationSetting[] $settings Get all settings.
82
 *
83
 * @version 1.0
84
 * @author vistart <[email protected]>
85
 */
86
class Organization extends User
87
{
88
    use SelfBlameableTrait;
89
90
    const TYPE_ORGANIZATION = 1;
91
    const TYPE_DEPARTMENT = 2;
92
93
    /**
94
     * @var boolean Organization does not need password and corresponding features.
95
     */
96
    public $passwordHashAttribute = false;
97
98
    /**
99
     * @var boolean Organization does not need password and corresponding features.
100
     */
101
    public $passwordResetTokenAttribute = false;
102
103
    /**
104
     * @var boolean Organization does not need password and corresponding features.
105
     */
106
    public $passwordHistoryClass = false;
107
108
    /**
109
     * @var boolean Organization does not need source.
110
     */
111
    public $sourceAttribute = false;
112
113
    /**
114
     * @var boolean Organization does not need auth key.
115
     */
116
    public $authKeyAttribute = false;
117
118
    /**
119
     * @var boolean Organization does not need access token.
120
     */
121
    public $accessTokenAttribute = false;
122
123
    /**
124
     * @var boolean Organization does not need login log.
125
     */
126
    public $loginLogClass = false;
127
128
    /**
129
     * @var string The Organization Profile Class
130
     */
131
    public $profileClass = Profile::class;
132
133
    /**
134
     * @var string The Member Class.
135
     */
136
    public $memberClass = Member::class;
137
138
    /**
139
     * @var string The Subordinate Limit Class
140
     */
141
    public $subordinateLimitClass = SubordinateLimit::class;
142
143
    /**
144
     * @var string The Member Limit Class
145
     */
146
    public $memberLimitClass = MemberLimit::class;
147
148
    /**
149
     * @var string The Organization Search Class
150
     */
151
    public $searchClass = OrganizationSearch::class;
152
153
    /**
154
     * @var string The Organization Setting Class
155
     */
156
    public $organizationSettingClass = OrganizationSetting::class;
157
158
    /**
159
     * @var Member
160
     */
161
    private $noInitMember;
162
163
    /**
164
     * @var SubordinateLimit
165
     */
166
    private $noInitSubordinateLimit;
167
168
    /**
169
     * @var MemberLimit
170
     */
171
    private $noInitMemberLimit;
172
173
    /**
174
     * @var OrganizationSetting
175
     */
176
    private $noInitOrganizationSetting;
177
178
    /**
179
     * @var User the creator of current Organization or Department.
180
     * This property is only available after registration.
181
     * Please do not access it at other times.
182
     * If you want to get creator model except registration, please
183
     * access [[$creator]] magic-property instead.
184
     */
185
    public $creatorModel;
186
187
    /**
188
     * @var array The configuration array of Organization Profile.
189
     * This property is only available after registration.
190
     * Please do not access it at other times.
191
     * If you want to get profile model except registration, please
192
     * access [[$profile]] magic-property instead.
193
     */
194
    public $profileConfig;
195
196
    const EVENT_BEFORE_ADD_MEMBER = 'eventBeforeAddMember';
197
    const EVENT_AFTER_ADD_MEMBER = 'eventAfterAddMember';
198
    const EVENT_BEFORE_REMOVE_MEMBER = 'eventBeforeRemoveMember';
199
    const EVENT_AFTER_REMOVE_MEMBER = 'eventAfterRemoveMember';
200
201
    public $cacheTagPrefix = 'tag_organization_';
202
203
    /**
204
     * @return Member
205
     */
206 51
    public function getNoInitMember()
207
    {
208 51
        if (!$this->noInitMember) {
209 51
            $class = $this->memberClass;
210 51
            $this->noInitMember = $class::buildNoInitModel();
211
        }
212 51
        return $this->noInitMember;
213
    }
214
215
    /**
216
     * @return SubordinateLimit
217
     */
218 2
    public function getNoInitSubordinateLimit()
219
    {
220 2
        if (!$this->noInitSubordinateLimit) {
221 2
            $class = $this->subordinateLimitClass;
222 2
            $this->noInitSubordinateLimit = $class::buildNoInitModel();
223
        }
224 2
        return $this->noInitSubordinateLimit;
225
    }
226
227
    /**
228
     * @return MemberLimit
229
     */
230 1
    public function getNoInitMemberLimit()
231
    {
232 1
        if (!$this->noInitMemberLimit) {
233 1
            $class = $this->memberLimitClass;
234 1
            $this->noInitMemberLimit = $class::buildNoInitModel();
235
        }
236 1
        return $this->noInitMemberLimit;
237
    }
238
239
    /**
240
     * @return null|OrganizationSetting
241
     */
242 31
    public function getNoInitOrganizationSetting()
243
    {
244 31
        if (!$this->noInitOrganizationSetting) {
245 31
            $class = $this->organizationSettingClass;
246 31
            if (empty($class)) {
247
                return null;
248
            }
249 31
            $this->noInitOrganizationSetting = $class::buildNoInitModel();
250
        }
251 31
        return $this->noInitOrganizationSetting;
252
    }
253
254
    /**
255
     * @return null|OrganizationSearch
256
     */
257
    public function getSearchModel()
258
    {
259
        $class = $this->searchClass;
260
        if (empty($class) || !class_exists($class)) {
261
            return null;
262
        }
263
        return new $class;
264
    }
265
266
    /**
267
     * @inheritdoc
268
     */
269 52
    public function init()
270
    {
271 52
        $this->parentAttribute = 'parent_guid';
272 52
        if (class_exists($this->memberClass)) {
273 52
            $this->addSubsidiaryClass('Member', ['class' => $this->memberClass]);
274
        }
275 52
        if ($this->skipInit) {
276 52
            return;
277
        }
278 52
        $this->on(static::$eventAfterRegister, [$this, 'onAddProfile'], $this->profileConfig);
279 52
        $this->on(static::$eventAfterRegister, [$this, 'onAssignCreator'], $this->creatorModel);
280 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeCreator']);
281 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokeAdministrators']);
282 52
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onRevokePermissions']);
283 52
        $this->initSelfBlameableEvents();
284 52
        parent::init();
285 52
    }
286
287
    /**
288
     * @inheritdoc
289
     */
290 1
    public function attributeLabels()
291
    {
292
        return [
293 1
            $this->guidAttribute => Yii::t('user', 'GUID'),
294 1
            $this->idAttribute => Yii::t('user', 'ID'),
295 1
            $this->ipAttribute => Yii::t('user', 'IP Address'),
296 1
            $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'),
297 1
            $this->parentAttribute => Yii::t('organization', 'Parent'),
298 1
            $this->createdAtAttribute => Yii::t('user', 'Creation Time'),
299 1
            $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'),
300 1
            $this->statusAttribute => Yii::t('user', 'Status'),
301 1
            'type' => Yii::t('user', 'Type'),
302 1
            'isExcludeOtherMembers' => Yii::t('organization', 'Exclude Other Members'),
303 1
            'isDisallowMemberJoinOther' => Yii::t('organization', 'Disallow Member to Join in Other Organizations'),
304 1
            'isOnlyAcceptCurrentOrgMember' => Yii::t('organization', 'Only Accept Current Organization Members'),
305 1
            'isOnlyAcceptSuperiorOrgMember' => Yii::t('organization', 'Only Accept Superior Organization Members'),
306
        ];
307
    }
308
309
    /**
310
     * @inheritdoc
311
     */
312 52
    public static function tableName()
313
    {
314 52
        return '{{%organization}}';
315
    }
316
317
    /**
318
     * Find.
319
     * Friendly to IDE.
320
     * @return OrganizationQuery
321
     */
322 52
    public static function find()
323
    {
324 52
        return parent::find();
325
    }
326
327
    /**
328
     * Get rules associated with type attribute.
329
     * @return array
330
     */
331 51
    protected function getTypeRules()
332
    {
333
        return [
334 51
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
335
            ['type', 'required'],
336 51
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
337
        ];
338
    }
339
340
    /**
341
     * @inheritdoc
342
     */
343 51
    public function rules()
344
    {
345 51
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
346
    }
347
348
    /**
349
     * Get Member Query.
350
     * @return MemberQuery
351
     */
352 50
    public function getMembers()
353
    {
354 50
        return $this->hasMany($this->memberClass, [
355 50
            $this->getNoInitMember()->createdByAttribute => $this->guidAttribute
356 50
        ])->inverseOf('organization');
357
    }
358
359
    /**
360
     * Get organization member users' query.
361
     * @return BaseUserQuery
362
     */
363 6
    public function getMemberUsers()
364
    {
365 6
        $noInit = $this->getNoInitMember();
366 6
        $class = $noInit->memberUserClass;
367 6
        $noInitUser = $class::buildNoInitModel();
368 6
        return $this->hasMany($class, [
369 6
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
370 6
        ])->via('members')->inverseOf('atOrganizations');
371
    }
372
373
    /**
374
     * Get subordinate limit query.
375
     * @return null|BaseBlameableQuery
376
     */
377 2
    public function getSubordinateLimit()
378
    {
379 2
        if (empty($this->subordinateLimitClass)) {
380
            return null;
381
        }
382 2
        return $this->hasOne($this->subordinateLimitClass, [
383 2
            $this->getNoInitSubordinateLimit()->createdByAttribute => $this->guidAttribute
384
        ]);
385
    }
386
387
    /**
388
     * Get member limit query.
389
     * @return null|BaseBlameableQuery
390
     */
391 1
    public function getMemberLimit()
392
    {
393 1
        if (empty($this->memberLimitClass)) {
394
            return null;
395
        }
396 1
        return $this->hasOne($this->memberLimitClass, [
397 1
            $this->getNoInitMemberLimit()->createdByAttribute => $this->guidAttribute
398
        ]);
399
    }
400
401
    /**
402
     * @param string|null $item If you want to get all settings, please set it null.
403
     * @return null
404
     */
405 31
    public function getSettings($item = null)
406
    {
407 31
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
408
            return null;
409
        }
410 31
        $query = $this->hasMany($this->organizationSettingClass, [$this->getNoInitOrganizationSetting()->createdByAttribute => $this->guidAttribute]);
411 31
        if (!empty($item)) {
412 31
            $query = $query->andWhere([$this->getNoInitOrganizationSetting()->idAttribute => $item]);
413
        }
414 31
        return $query;
415
    }
416
417
    /**
418
     * Set organization setting.
419
     * @param string $item
420
     * @param string $value
421
     * @param bool $unique
422
     * @return bool|null Null if organization setting not enabled.
423
     * @throws IntegrityException throw if "item-value" unique broke.
424
     */
425 31
    public function setSetting($item, $value, $unique = false)
426
    {
427 31
        if (empty($this->organizationSettingClass) || !is_string($this->organizationSettingClass)) {
428
            return null;
429
        }
430 31
        $setting = $this->getSettings($item)->one();
431
        /* @var $setting OrganizationSetting */
432 31
        if (!$setting) {
433 31
            $setting = $this->create($this->organizationSettingClass, [
434 31
                $this->getNoInitOrganizationSetting()->idAttribute => $item,
435
            ]);
436
        }
437 31
        $setting->value = $value;
438 31
        if ($unique) {
439
            $class = $this->organizationSettingClass;
440
            if ($class::find()->andWhere([
441
                $this->getNoInitOrganizationSetting()->idAttribute => $item,
442
                $this->getNoInitOrganizationSetting()->contentAttribute => $value
443
            ])->exists()) {
444
                throw new IntegrityException("`$item` : `$value` existed.");
445
            }
446
        }
447 31
        return $setting->save();
448
    }
449
450
    /**
451
     * Get member with specified user.
452
     * @param User|string|integer $user
453
     * @return Member Null if `user` is not in this organization.
454
     */
455 50
    public function getMember($user)
456
    {
457 50
        return $this->getMembers()->user($user)->one();
458
    }
459
460
    /**
461
     * Add member to organization.
462
     * @param Member|User|string|integer $member Member or User model, or User ID or GUID.
463
     * If member is created, it will be re-assigned to this parameter.
464
     * @see createMemberModel
465
     * @see createMemberModelWithUser
466
     * @return boolean
467
     * @throws DisallowMemberJoinOtherException
468
     * @throws ExcludeOtherMembersException
469
     * @throws OnlyAcceptCurrentOrgMemberException
470
     * @throws OnlyAcceptSuperiorOrgMemberException
471
     */
472 50
    public function addMember(&$member)
473
    {
474 50
        if ($this->getIsNewRecord()) {
475
            return false;
476
        }
477 50
        if ($this->hasReachedMemberLimit()) {
478 1
            return false;
479
        }
480 50
        $user = null;
481 50
        if ($member instanceof Member) {
482
            if ($member->getIsNewRecord()) {
483
                return false;
484
            }
485
            $user = $member->memberUser;
486
        }
487 50
        if ($member instanceof User) {
488 50
            $user = $member;
489
        }
490 50
        if (is_string($member) || is_int($member)) {
491
            $class = Yii::$app->user->identityClass;
492
            $user = $class::find()->guidOrId($member)->one();
493
        }
494 50
        if ($this->hasMember($user)) {
495
            return false;
496
        }
497 50
        $orgs = $user->getAtOrganizations()->all();
498
        /* @var $orgs Organization[] */
499 50
        foreach ($orgs as $org) {
500 31
            if ($org->topOrganization->isDisallowMemberJoinOther && !$org->topOrganization->equals($this->topOrganization)) {
501 1
                throw new DisallowMemberJoinOtherException(Yii::t('organization', "An organization in which the user is located does not allow its members to join other organizations."));
502
            }
503 31
            if ($this->topOrganization->isExcludeOtherMembers && !$org->topOrganization->equals($this->topOrganization)) {
504 31
                throw new ExcludeOtherMembersException(Yii::t('organization', "The organization does not allow users who have joined other organizations to join."));
505
            }
506
        }
507 50
        if ($this->isDepartment() && $this->isOnlyAcceptCurrentOrgMember && !$this->topOrganization->hasMember($user)) {
508 1
            throw new OnlyAcceptCurrentOrgMemberException(Yii::t('organization' ,'This department is only accepted by members of the organization.'));
509
        }
510 50
        if ($this->isDepartment() && !$this->parent->equals($this->topOrganization) && $this->isOnlyAcceptSuperiorOrgMember && !$this->parent->hasMember($user)) {
511 1
            throw new OnlyAcceptSuperiorOrgMemberException(Yii::t('organization', 'This department only accepts members of the parent organization or department.'));
512
        }
513
514 50
        $this->trigger(self::EVENT_BEFORE_ADD_MEMBER);
515 50
        $model = null;
516 50
        if ($member instanceof Member) {
517
            $model = $this->createMemberModel($member);
518 50
        } elseif (($member instanceof User) || is_string($member) || is_int($member)) {
519 50
            $model = $this->createMemberModelWithUser($member);
520
        }
521 50
        $member = $model;
522 50
        $result = ($member instanceof Member) ? $member->save() : false;
523 50
        $this->trigger(self::EVENT_AFTER_ADD_MEMBER);
524 50
        return $result;
525
    }
526
527
    /**
528
     * Create member model, and set organization with this.
529
     * @param Member $member If this parameter is not new record, it's organization
530
     * will be set with this, and return it. Otherwise, it will extract `User`
531
     * model and create new `Member` model.
532
     * @see createMemberModelWithUser
533
     * @return Member
534
     */
535
    public function createMemberModel($member)
536
    {
537
        if (!$member->getIsNewRecord()) {
538
            $member->setOrganization($this);
539
            return $member;
540
        }
541
        return $this->createMemberModelWithUser($member->memberUser);
542
    }
543
544
    /**
545
     * Create member model with user, and set organization with this.
546
     * @param User|string|integer $user
547
     * @return Member
548
     */
549 50
    public function createMemberModelWithUser($user)
550
    {
551
        $config = [
552 50
            'memberUser' => $user,
553 50
            'organization' => $this,
554 50
            'nickname' => '',
555
        ];
556 50
        $member = $this->createMember($config);
557 50
        $member->nickname = $member->memberUser->profile->nickname;
558 50
        return $member;
559
    }
560
561
    /**
562
     * Remove member.
563
     * Note: the creator cannot be removed.
564
     * @param Member|User $member
565
     * @return boolean
566
     */
567 4
    public function removeMember(&$member)
568
    {
569 4
        if ($this->getIsNewRecord()) {
570
            return false;
571
        }
572 4
        $this->trigger(self::EVENT_BEFORE_REMOVE_MEMBER);
573 4
        if ($member instanceof $this->memberClass) {
574 4
            $member = $member->{$member->memberAttribute};
575
        }
576 4
        $member = $this->getMember($member);
577 4
        if (!$member || $member->isCreator()) {
578
            return false;
579
        }
580 4
        $result = $member->delete() > 0;
581 4
        $this->trigger(self::EVENT_AFTER_REMOVE_MEMBER);
582 4
        return $result;
583
    }
584
585
    /**
586
     * Remove administrator.
587
     * @param Member|User|integer|string $member Member instance, or User instance or its GUID or ID.
588
     * @param boolean $keep Keep member after administrator being revoked.
589
     * @return boolean
590
     * @throws IntegrityException
591
     */
592
    public function removeAdministrator(&$member, $keep = true)
593
    {
594
        if ($this->getIsNewRecord()) {
595
            return false;
596
        }
597
        if ($member instanceof $this->memberClass) {
598
            $member = $member->{$member->memberAttribute};
599
        }
600
        $member = $this->getMember($member);
601
        if ($member && $member->isAdministrator()) {
602
            if ($keep) {
603
                return $member->revokeAdministrator();
604
            }
605
            return $this->removeMember($member);
606
        }
607
        return false;
608
    }
609
610
    /**
611
     * 
612
     * @param Event $event
613
     * @throws IntegrityException
614
     * @return boolean
615
     */
616 51
    public function onAddProfile($event)
617
    {
618 51
        $profile = $event->sender->createProfile($event->data);
619 51
        if (!$profile->save()) {
620
            throw new IntegrityException('Profile Save Failed.');
621
        }
622 51
        return true;
623
    }
624
625
    /**
626
     * 
627
     * @param Event $event
628
     */
629 51
    public function onAssignCreator($event)
630
    {
631 51
        return $event->sender->addCreator($event->data);
632
    }
633
634
    /**
635
     * 
636
     * @param Event $event
637
     * @return boolean
638
     */
639 20
    public function onRevokeCreator($event)
640
    {
641 20
        $sender = $event->sender;
642
        /* @var $sender static */
643 20
        $member = $sender->getMemberCreators()->one();
644
        /* @var $member Member */
645 20
        $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
646 20
        return $member->revokeRole($role);
647
    }
648
649
    /**
650
     * 
651
     * @param Event $event
652
     * @return boolean
653
     */
654 20
    public function onRevokeAdministrators($event)
655
    {
656 20
        $sender = $event->sender;
657
        /* @var $sender static */
658 20
        $members = $sender->getMemberAdministrators()->all();
659
        /* @var $members Member[] */
660 20
        foreach ($members as $member)
661
        {
662 1
            $member->revokeAdministrator();
663
        }
664 20
        return true;
665
    }
666
667
    /**
668
     * 
669
     * @param Event $event
670
     */
671 20
    public function onRevokePermissions($event)
672
    {
673
        
674 20
    }
675
676
    /**
677
     * Check whether current instance is an organization.
678
     * @return boolean
679
     */
680 50
    public function isOrganization()
681
    {
682 50
        return $this->type == static::TYPE_ORGANIZATION;
683
    }
684
685
    /**
686
     * Check whether current instance if a department.
687
     * @return boolean
688
     */
689 50
    public function isDepartment()
690
    {
691 50
        return $this->type == static::TYPE_DEPARTMENT;
692
    }
693
694
    /**
695
     * Check whether the current organization has a member.
696
     * @param User|string|integer $user User instance, GUID or ID.
697
     * @return boolean
698
     */
699 50
    public function hasMember($user)
700
    {
701 50
        return !empty($this->getMember($user));
702
    }
703
704
    /**
705
     * Get member query which role is specified `Creator`.
706
     * @return MemberQuery
707
     */
708 24
    public function getMemberCreators()
709
    {
710 24
        return $this->getMembers()->andWhere(['role' => [(new DepartmentCreator)->name, (new OrganizationCreator)->name]]);
711
    }
712
713
    /**
714
     * Get member query which role is specified `Administrator`.
715
     * @return MemberQuery
716
     */
717 22
    public function getMemberAdministrators()
718
    {
719 22
        return $this->getMembers()->andWhere(['role' => [(new DepartmentAdmin)->name, (new OrganizationAdmin)->name]]);
720
    }
721
722
    /**
723
     * Get user query which role is specified `Creator`.
724
     * @return BaseUserQuery
725
     */
726 4
    public function getCreator()
727
    {
728 4
        $noInit = $this->getNoInitMember();
729 4
        $class = $noInit->memberUserClass;
730 4
        $noInitUser = $class::buildNoInitModel();
731 4
        return $this->hasOne($class, [
732 4
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
733 4
        ])->via('memberCreators')->inverseOf('creatorsAtOrganizations');
734
    }
735
736
    /**
737
     * Get user query which role is specified `Administrator`.
738
     * @return BaseUserQuery
739
     */
740 2
    public function getAdministrators()
741
    {
742 2
        $noInit = $this->getNoInitMember();
743 2
        $class = $noInit->memberUserClass;
744 2
        $noInitUser = $class::buildNoInitModel();
745 2
        return $this->hasMany($class, [
746 2
            $noInitUser->guidAttribute => $this->getNoInitMember()->memberAttribute
747 2
        ])->via('memberAdministrators')->inverseOf('administratorsAtOrganizations');
748
    }
749
750
    /**
751
     * 
752
     * @param User $user
753
     * @return boolean
754
     * @throws \Exception
755
     * @throws IntegrityException
756
     */
757 51
    protected function addCreator($user)
758
    {
759 51
        if (!$user) {
760 1
            throw new InvalidParamException('Creator Invalid.');
761
        }
762 50
        $member = $user;
763 50
        $transaction = Yii::$app->db->beginTransaction();
764
        try {
765 50
            if (!$this->addMember($member)) {
766
                throw new IntegrityException('Failed to add member.');
767
            }
768 50
            $role = $this->isOrganization() ? (new OrganizationCreator)->name : (new DepartmentCreator)->name;
769 50
            $member->assignRole($role);
770 50
            if (!$member->save()) {
771
                throw new IntegrityException('Failed to assign creator.');
772
            }
773 50
            $transaction->commit();
774
        } catch (\Exception $ex) {
775
            $transaction->rollBack();
776
            Yii::error($ex->getMessage(), __METHOD__);
777
            throw $ex;
778
        }
779 50
        return true;
780
    }
781
782
    /**
783
     * Add administrator.
784
     * @param User|integer|string $user User instance, or its GUID or ID.
785
     * @return boolean
786
     * @throws \Exception
787
     * @throws IntegrityException
788
     */
789 17
    public function addAdministrator($user)
790
    {
791 17
        $transaction = Yii::$app->db->beginTransaction();
792
        try {
793 17
            if (!$this->hasMember($user) && !$this->addMember($user)) {
794
                throw new IntegrityException(Yii::t('organization', 'Failed to add member.'));
795
            }
796 17
            $member = $this->getMember($user);
797 17
            $member->assignAdministrator();
798 17
            $transaction->commit();
799 2
        } catch (\Exception $ex) {
800 2
            $transaction->rollBack();
801 2
            Yii::error($ex->getMessage(), __METHOD__);
802 2
            throw $ex;
803
        }
804 17
        return true;
805
    }
806
807
    /**
808
     * Check whether the current organization has administrator.
809
     * @param User|integer|string $user
810
     * @return boolean
811
     */
812 2
    public function hasAdministrator($user)
813
    {
814 2
        $member = $this->getMember($user);
815 2
        if (!$member) {
816
            return false;
817
        }
818 2
        return $member->isAdministrator();
819
    }
820
821
    /**
822
     * Check whether this organization has reached the upper limit of subordinates.
823
     * @return boolean
824
     */
825 19
    public function hasReachedSubordinateLimit()
826
    {
827 19
        $remaining = $this->getRemainingSubordinatePlaces();
828 19
        if ($remaining === false) {
829
            return false;
830
        }
831 19
        return $remaining <= 0;
832
    }
833
834
    /**
835
     * Get the remaining places of subordinates.
836
     * @return bool|int False if no limit
837
     */
838 19
    public function getRemainingSubordinatePlaces()
839
    {
840 19
        $class = $this->subordinateLimitClass;
841 19
        if (empty($class)) {
842
            return false;
843
        }
844 19
        $limit = $class::getLimit($this);
845 19
        if ($limit === false) {
846
            return false;
847
        }
848 19
        $count = (int)$this->getChildren()->count();
849 19
        return $limit - $count;
850
    }
851
852
    /**
853
     * Check whether this organization has reached the upper limit of members.
854
     * @return boolean
855
     */
856 50
    public function hasReachedMemberLimit()
857
    {
858 50
        $remaining = $this->getRemainingMemberPlaces();
859 50
        if ($remaining === false) {
860
            return false;
861
        }
862 50
        return $remaining <= 0;
863
    }
864
865
    /**
866
     * Get the remaining places of members.
867
     * @return bool|int False if no limit.
868
     */
869 50
    public function getRemainingMemberPlaces()
870
    {
871 50
        $class = $this->memberLimitClass;
872 50
        if (empty($class)) {
873
            return false;
874
        }
875 50
        $limit = $class::getLimit($this);
876 50
        if ($limit === false) {
877
            return false;
878
        }
879 50
        $count = (int)$this->getMembers()->count();
880 50
        return $limit - $count;
881
    }
882
883
    const SETTING_ITEM_EXCLUDE_OTHER_MEMBERS = 'exclude_other_members';
884
885
    /**
886
     * @return bool
887
     */
888 31
    public function getIsExcludeOtherMembers()
889
    {
890 31
        $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
891 31
        if (!$setting) {
892 31
            $this->setIsExcludeOtherMembers(false);
893 31
            $setting = $this->getSettings(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS)->one();
894
        }
895 31
        return $setting->value == '1';
896
    }
897
898
    /**
899
     * @param bool $value
900
     * @return bool
901
     */
902 31
    public function setIsExcludeOtherMembers($value = true)
903
    {
904 31
        return $this->setSetting(static::SETTING_ITEM_EXCLUDE_OTHER_MEMBERS, $value ? '1' : '0');
905
    }
906
907
    const SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER = 'disallow_member_join_other';
908
909
    /**
910
     * @return bool
911
     */
912 31
    public function getIsDisallowMemberJoinOther()
913
    {
914 31
        $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
915 31
        if (!$setting) {
916 31
            $this->setIsDisallowMemberJoinOther(false);
917 31
            $setting = $this->getSettings(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER)->one();
918
        }
919 31
        return $setting->value == '1';
920
    }
921
922
    /**
923
     * @param bool $value
924
     * @return bool
925
     */
926 31
    public function setIsDisallowMemberJoinOther($value = true)
927
    {
928 31
        return $this->setSetting(static::SETTING_ITEM_DISALLOW_MEMBER_JOIN_OTHER, $value ? '1' : '0');
929
    }
930
931
    const SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER = 'only_accept_current_org_member';
932
933
    /**
934
     * @return bool
935
     */
936 18
    public function getIsOnlyAcceptCurrentOrgMember()
937
    {
938 18
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
939 18
        if (!$setting) {
940 18
            $this->setIsOnlyAcceptCurrentOrgMember(false);
941 18
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER)->one();
942
        }
943 18
        return $setting->value == '1';
944
    }
945
946
    /**
947
     * @param bool $value
948
     * @return bool
949
     */
950 18
    public function setIsOnlyAcceptCurrentOrgMember($value = true)
951
    {
952 18
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_CURRENT_ORG_MEMBER, $value ? '1' : '0');
953
    }
954
955
    const SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER = 'only_accept_superior_org_member';
956
957
    /**
958
     * @return bool
959
     */
960 10
    public function getIsOnlyAcceptSuperiorOrgMember()
961
    {
962 10
        if ($this->parent && $this->parent->equals($this->topOrganization)) {
963
            return $this->getIsOnlyAcceptCurrentOrgMember();
964
        }
965 10
        $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
966 10
        if (!$setting) {
967 10
            $this->setIsOnlyAcceptSuperiorOrgMember(false);
968 10
            $setting = $this->getSettings(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER)->one();
969
        }
970 10
        return $setting->value == '1';
971
    }
972
973
    /**
974
     * @param bool $value
975
     * @return bool
976
     */
977 10
    public function setIsOnlyAcceptSuperiorOrgMember($value = true)
978
    {
979 10
        if ($this->parent && $this->parent->equals($this->topOrganization)) {
980
            return $this->setIsOnlyAcceptCurrentOrgMember($value);
981
        }
982 10
        return $this->setSetting(static::SETTING_ITEM_ONLY_ACCEPT_SUPERIOR_ORG_MEMBER, $value ? '1' : '0');
983
    }
984
985
    const SETTING_ITEM_JOIN_PASSWORD = 'join_password';
986
987
    /**
988
     * Get join password.
989
     * @return mixed
990
     */
991
    public function getJoinPassword()
992
    {
993
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_PASSWORD)->one();
994
        if (!$setting) {
995
            $this->setJoinPassword();
996
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_PASSWORD)->one();
997
        }
998
        return $setting->value;
999
    }
1000
1001
    /**
1002
     * Set join password.
1003
     * @param string $value
1004
     * @return bool|null
1005
     */
1006
    public function setJoinPassword($value = '')
1007
    {
1008
        return $this->setSetting(static::SETTING_ITEM_JOIN_PASSWORD, $value);
1009
    }
1010
1011
    const SETTING_ITEM_JOIN_IP_ADDRESS = 'join_ip_address';
1012
1013
    /**
1014
     * Get Join IP address
1015
     * @return mixed
1016
     */
1017
    public function getJoinIpAddress()
1018
    {
1019
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_IP_ADDRESS)->one();
1020
        if (!$setting) {
1021
            $this->setJoinIpAddress();
1022
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_IP_ADDRESS)->one();
1023
        }
1024
        return $setting->value;
1025
    }
1026
1027
    /**
1028
     * Set join IP address.
1029
     * @param $value
1030
     * @return bool|null
1031
     */
1032
    public function setJoinIpAddress($value = '')
1033
    {
1034
        return $this->setSetting(static::SETTING_ITEM_JOIN_IP_ADDRESS, $value);
1035
    }
1036
1037
    const SETTING_ITEM_JOIN_ENTRANCE_URL = 'join_entrance_url';
1038
1039
    /**
1040
     * Get join entrance URL.
1041
     * This setting should be confirmed unique.
1042
     * @return string
1043
     */
1044
    public function getJoinEntranceUrl()
1045
    {
1046
        $setting = $this->getSettings(static::SETTING_ITEM_JOIN_ENTRANCE_URL)->one();
1047
        if (!$setting) {
1048
            $this->setJoinEntranceUrl();
1049
            $setting = $this->getSettings(static::SETTING_ITEM_JOIN_ENTRANCE_URL)->one();
1050
        }
1051
        return $setting->value;
1052
    }
1053
1054
    /**
1055
     * Set join entrance URL.
1056
     * @param string $value
1057
     * @return bool|null
1058
     */
1059
    public function setJoinEntranceUrl($value = '')
1060
    {
1061
        return $this->setSetting(static::SETTING_ITEM_JOIN_ENTRANCE_URL, $value, !empty($value));
1062
    }
1063
1064
    const SETTING_ITEM_EXIT_ALLOW_WITHDRAW_ACTIVELY = 'exit_allow_withdraw_actively';
1065
1066
    /**
1067
     * @return bool
1068
     */
1069
    public function getExitAllowWithdrawActively()
1070
    {
1071
        $setting = $this->getSettings(static::SETTING_ITEM_EXIT_ALLOW_WITHDRAW_ACTIVELY)->one();
1072
        if (!$setting) {
1073
            $this->setExitAllowWithdrawActively();
1074
            $setting = $this->getSettings(static::SETTING_ITEM_EXIT_ALLOW_WITHDRAW_ACTIVELY)->one();
1075
        }
1076
        return $setting->value == '1';
1077
    }
1078
1079
    /**
1080
     * @param bool $value
1081
     * @return bool|null
1082
     */
1083
    public function setExitAllowWithdrawActively($value = false)
1084
    {
1085
        return $this->setSetting(static::SETTING_ITEM_EXIT_ALLOW_WITHDRAW_ACTIVELY, $value ? '1' : '0');
1086
    }
1087
1088
    /**
1089
     * @return $this|null|static
1090
     */
1091 31
    public function getTopOrganization()
1092
    {
1093 31
        if ($this->isOrganization()) {
1094 31
            return $this;
1095
        }
1096 18
        $chain = $this->getAncestorChain();
1097 18
        return static::findOne(end($chain));
1098
    }
1099
1100
    /**
1101
     * Check whether the subordinates have the [[$user]]
1102
     * Note, this operation may consume the quantity of database selection.
1103
     * @param User $user
1104
     * @return bool
1105
     */
1106 2
    public function hasMemberInSubordinates($user)
1107
    {
1108 2
        if ($this->getChildren()->joinWith(['memberUsers mu_alias'])
1109 2
            ->andWhere(['mu_alias.' . $user->guidAttribute => $user->getGUID()])->exists()) {
1110 1
            return true;
1111
        }
1112 2
        $children = $this->children;
1113
        /* @var $children static[] */
1114 2
        foreach ($children as $child) {
1115 2
            if ($child->hasMemberInSubordinates($user)) {
1116 2
                return true;
1117
            }
1118
        }
1119 2
        return false;
1120
    }
1121
}
1122