rhosocial/yii2-organization

View on GitHub
Member.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

/**
 *  _   __ __ _____ _____ ___  ____  _____
 * | | / // // ___//_  _//   ||  __||_   _|
 * | |/ // /(__  )  / / / /| || |     | |
 * |___//_//____/  /_/ /_/ |_||_|     |_|
 * @link https://vistart.me/
 * @copyright Copyright (c) 2016 - 2017 vistart
 * @license https://vistart.me/license/
 */

namespace rhosocial\organization;

use rhosocial\base\models\models\BaseBlameableModel;
use rhosocial\user\rbac\Item;
use rhosocial\user\rbac\Role;
use rhosocial\user\User;
use rhosocial\organization\rbac\roles\DepartmentAdmin;
use rhosocial\organization\rbac\roles\DepartmentCreator;
use rhosocial\organization\rbac\roles\OrganizationAdmin;
use rhosocial\organization\rbac\roles\OrganizationCreator;
use rhosocial\organization\queries\OrganizationQuery;
use rhosocial\organization\queries\MemberQuery;
use Yii;
use yii\base\InvalidCallException;
use yii\base\InvalidParamException;
use yii\base\InvalidValueException;
use yii\db\IntegrityException;

/**
 * Organization member.
 *
 * @property string $organization_guid
 * @property string $user_guid store guid of user who represents this member.
 * @property string $nickname
 * @property string $role
 * @property string $position
 * @property string $description
 * 
 * @property string $department_guid
 * @property string $member_guid
 * @property Organization $organization
 * @property User $memberUser
 *
 * @version 1.0
 * @author vistart <i@vistart.me>
 */
class Member extends BaseBlameableModel
{
    public $createdByAttribute = 'organization_guid';
    public $updatedByAttribute = false;
    public $hostClass = Organization::class;

    public $memberAttribute = 'user_guid';
    public $memberUserClass = User::class;
    public $contentAttribute = 'nickname';
    public $searchClass = MemberSearch::class;
    private $noInitMemberUser;
    /**
     * @return User
     */
    public function getNoInitMemberUser()
    {
        if (!$this->noInitMemberUser) {
            $class = $this->memberUserClass;
            $this->noInitMemberUser = $class::buildNoInitModel();
        }
        return $this->noInitMemberUser;
    }

    /**
     * @return null|MemberSearch
     */
    public function getSearchModel()
    {
        $class = $this->searchClass;
        if (empty($class) || !class_exists($class)) {
            return null;
        }
        return new $class;
    }

    /**
     * @inheritdoc
     */
    public function init()
    {
        if (!is_string($this->queryClass)) {
            $this->queryClass = MemberQuery::class;
        }
        if ($this->skipInit) {
            return;
        }
        parent::init();
    }

    public $descriptionAttribute = 'description';

    public function getMemberUserRules()
    {
        return [
            [$this->memberAttribute, 'required'],
            [$this->memberAttribute, 'string', 'max' => 36],
        ];
    }

    public function getMemberRoleRules()
    {
        return [
            ['role', 'string', 'max' => 255],
        ];
    }

    public function getMemberPositionRules()
    {
        return [
            ['position', 'default', 'value' => ''],
            ['position', 'string', 'max' => 255],
        ];
    }

    /**
     * Set member user.
     * @param User|string|integer $user
     */
    public function setMemberUser($user)
    {
        $class = $this->memberUserClass;
        if (is_numeric($user)) {
            $user = $class::find()->id($user)->one();
        }
        if ($user instanceof $class) {
            $user = $user->getGUID();
        }
        $this->{$this->memberAttribute} = $user;
    }

    public function getMemberUser()
    {
        return $this->hasOne($this->memberUserClass, [$this->getNoInitMemberUser()->guidAttribute => $this->memberAttribute]);
    }

    /**
     * Get Organization Query.
     * Alias of `getHost` method.
     * @return OrganizationQuery
     */
    public function getOrganization()
    {
        return $this->getHost();
    }

    /**
     * Set Organization.
     * @param BaseOrganization $organization
     * @return boolean
     */
    public function setOrganization($organization)
    {
        return $this->setHost($organization);
    }

    /**
     * Assign role.
     * The setting role operation will not take effect immediately. You should
     * wrap this method and the subsequent save operations together into a
     * transaction, in order to ensure data cosistency.
     * @param Role $role
     * @return boolean
     */
    public function assignRole($role)
    {
        $user = $this->memberUser;
        if (!$user) {
            throw new InvalidValueException('Invalid User');
        }
        if ($role instanceof Item) {
            $role = $role->name;
        }
        $assignment = Yii::$app->authManager->getAssignment($role, $user);
        if (!$assignment) {
            $assignment = Yii::$app->authManager->assign($role, $user->getGUID());
        }
        return $this->setRole($role);
    }

    /**
     * Assign administrator.
     * @return boolean
     * @throws \Exception
     * @throws IntegrityException
     */
    public function assignAdministrator()
    {
        $host = $this->organization;
        /* @var $host Organization */
        if ($this->isCreator()) {
            throw new InvalidCallException(Yii::t('organization', 'The user is already a creator.'));
        }
        if ($this->isAdministrator()) {
            throw new InvalidCallException(Yii::t('organization', 'The user is already an administrator.'));
        }
        $role = null;
        if ($host->type == Organization::TYPE_ORGANIZATION) {
            $role = new OrganizationAdmin();
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
            $role = new DepartmentAdmin();
        }
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $this->assignRole($role);
            if (!$this->save()) {
                throw new IntegrityException(Yii::t('organization', 'Failed to assign administrator.'));
            }
            $transaction->commit();
        } catch (\Exception $ex) {
            $transaction->rollBack();
            Yii::error($ex->getMessage(), __METHOD__);
            throw $ex;
        }
        return true;
    }

    /**
     * Set role.
     * @param Role $role
     * @return boolean
     */
    public function setRole($role = null)
    {
        if (empty($role)) {
            $role = '';
        }
        if ($role instanceof Item) {
            $role = $role->name;
        }
        $this->role = $role;
        return true;
    }

    /**
     * Revoke role.
     * @param Role $role
     * @throws InvalidParamException
     * @throws IntegrityException
     * @throws \Exception
     * @return boolean
     */
    public function revokeRole($role)
    {
        $user = $this->memberUser;
        if (!$user) {
            throw new InvalidValueException('Invalid User');
        }
        if ($role instanceof Item) {
            $role = $role->name;
        }
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $assignment = Yii::$app->authManager->getAssignment($role, $user);
            if ($assignment) {
                $count = (int)($user->getOfMembers()->role($role)->count());
                if ($count <= 1) {
                    Yii::$app->authManager->revoke($role, $user);
                }
            }
            $this->setRole();
            if (!$this->save()) {
                throw new IntegrityException('Save failed.');
            }
            $transaction->commit();
        } catch (\Exception $ex) {
            $transaction->rollBack();
            Yii::error($ex->getMessage(), __METHOD__);
            throw $ex;
        }
        return true;
    }

    /**
     * Revoke administrator.
     * @return boolean
     * @throws IntegrityException
     * @throws \Exception
     */
    public function revokeAdministrator()
    {
        $host = $this->organization;
        /* @var $host Organization */
        if ($this->isCreator()) {
            throw new InvalidCallException(Yii::t('organization', 'The user is already a creator.'));
        }
        if (!$this->isAdministrator()) {
            throw new InvalidCallException(Yii::t('organization', 'The user is not administrator yet.'));
        }
        $role = null;
        if ($host->type == Organization::TYPE_ORGANIZATION) {
            $role = new OrganizationAdmin();
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
            $role = new DepartmentAdmin();
        }
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $this->revokeRole($role);
            if (!$this->save()) {
                throw new IntegrityException("Failed to revoke administrator.");
            }
            $transaction->commit();
        } catch (\Exception $ex) {
            $transaction->rollBack();
            Yii::error($ex->getMessage(), __METHOD__);
            throw $ex;
        }
        return true;
    }

    public function rules()
    {
        return array_merge($this->getMemberUserRules(), $this->getMemberRoleRules(), $this->getMemberPositionRules(), parent::rules());
    }

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%organization_member}}';
    }

    /**
     * Find.
     * Friendly to IDE.
     * @return MemberQuery
     */
    public static function find()
    {
        return parent::find();
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            $this->guidAttribute => Yii::t('user', 'GUID'),
            $this->idAttribute => Yii::t('user', 'ID'),
            $this->createdByAttribute => Yii::t('organization', 'Organization GUID'),
            $this->memberAttribute => Yii::t('organization', 'User GUID'),
            $this->contentAttribute => Yii::t('user', 'Nickname'),
            'role' => Yii::t('organization', 'Role'),
            'position' => Yii::t('organization', 'Member Position'),
            $this->descriptionAttribute => Yii::t('organization', 'Description'),
            $this->ipAttribute => Yii::t('user', 'IP Address'),
            $this->ipTypeAttribute => Yii::t('user', 'IP Address Type'),
            $this->createdAtAttribute => Yii::t('organization', 'Join Time'),
            $this->updatedAtAttribute => Yii::t('user', 'Last Updated Time'),
        ];
    }

    /**
     * @return bool
     */
    public function isDepartmentAdministrator()
    {
        return $this->role == (new DepartmentAdmin)->name;
    }

    /**
     * @return bool
     */
    public function isDepartmentCreator()
    {
        return $this->role == (new DepartmentCreator)->name;
    }

    /**
     * @return bool
     */
    public function isOrganizationAdministrator()
    {
        return $this->role == (new OrganizationAdmin)->name;
    }

    /**
     * @return bool
     */
    public function isOrganizationCreator()
    {
        return $this->role == (new OrganizationCreator)->name;
    }

    /**
     * Check whether current member is administrator.
     * @return boolean
     */
    public function isAdministrator()
    {
        return $this->isDepartmentAdministrator() || $this->isOrganizationAdministrator();
    }

    /**
     * Check whether current member is creator.
     * @return boolean
     */
    public function isCreator()
    {
        return $this->isDepartmentCreator() || $this->isOrganizationCreator();
    }

    /**
     * We think it a `member` if `role` property is empty.
     * @return boolean
     */
    public function isOnlyMember()
    {
        return empty($this->role);
    }

    const SCENARIO_UPDATE = 'update';
    const SCENARIO_ADMIN_UPDATE = 'admin_update';

    public function scenarios()
    {
        return array_merge(parent::scenarios(), [
            self::SCENARIO_UPDATE => [$this->contentAttribute,],
        ], [
            self::SCENARIO_ADMIN_UPDATE => [$this->contentAttribute, 'position', $this->descriptionAttribute],
        ]);
    }
}