<?php
/**
 * @link      http://www.u-bo.com
 * @copyright 南京友博网络科技有限公司 
 * @license   http://www.u-bo.com/license/
 */

namespace qcbase\models;

use Yii;
use yii\db\Query;
use yii\helpers\Url;
use yii\helpers\ArrayHelper;
use common\components\Tree;

/**
 * 
 * 后台菜单表（{{%menu}}）
 *
 * @property smallint $menu_id  (菜单表主键)
 * @property string $name  (菜单名称)
 * @property smallint $parent_id  (父级id)
 * @property string $parent_ids  (父级所有类别项目id)
 * @property string $child_ids  (所有子项目类别id)
 * @property string $module  (模块名称)
 * @property string $controller  (控制器名称)
 * @property string $action  (动作名称)
 * @property string $param  (附加参数)
 * @property smallint $sort  (排序)
 * @property smallint $status  (状态)
 * @property smallint $is_auth  (类型)
 * @property tinyint $show_scope  (是否显示权限范围)
 * @property varchar $remark  (备注)
 * 
 * @author HuLimin http://blog.u-bo.com
 * @since 0.1
 */
class Menu extends \yii\db\ActiveRecord
{
    public $menu = []; //菜单
    public $navList = []; //导航菜单
    public $child_actions; //子菜单

    const STATUS_ENABLE = 1; //启用
    const STATUS_DISABLE = 0; //禁用
    const ISAUTH_ENABLE = 1; //权限控制+菜单
    const ISAUTH_DISABLE = 2; //仅作菜单
    const SHOW_SCOPE = 1; //显示范围
    const HIDDEN_SCOPE = 0; //隐藏范围

    /**
     * @var array 共享的当前所有数据列表
     */
    public static $list;

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

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['name', 'module', 'controller', 'action', 'app_name'], 'required'],
            [['parent_id', 'sort', 'status', 'is_auth', 'show_scope'], 'integer'],
            [['name', 'module', 'controller', 'action'], 'string', 'max' => 50],
            [['param'], 'string', 'max' => 100],
            [['remark'], 'string', 'max' => 255],
            [['module', 'controller', 'action', 'app_name'], 'trim'],
            [['child_actions'], 'safe']
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'menu_id' => 'Menu',
            'app_name' => '所属应用',
            'name' => '菜单名称',
            'parent_id' => '父级菜单',
            'parent_ids' => '父级所有id集',
            'child_ids' => '子集所有id集',
            'module' => '模块名称',
            'controller' => '控制器名称',
            'action' => '动作名称',
            'param' => '附加参数',
            'sort' => '排序',
            'status' => '是否显示菜单',
            'is_auth' => '类型',
            'show_scope' => '授权是否显示权限范围',
            'child_actions' => '同步添加子菜单',
            'remark' => '备注'
        ];
    }

    /**
     * 公共搜索所需信息
     */
    public function search()
    {
        return [
            'app_name' => '应用名称',
            'name' => '菜单名称',
            'module' => '模型名称',
            'controller' => '控制器名称',
            'action' => '动作名称',
        ];
    }

    /**
     * 获取菜单
     *
     * @param char $field 查询字段，默认所有*
     * @param aray $map 查询条件
     * @param type $indexBy (是否返回带索引菜单 键名)
     */
    public static function getMenu($map = [], $order = ['sort' => SORT_ASC, 'menu_id' => SORT_ASC], $field = "*", $params = [], $indexBy = '', $distinct = '')
    {
        $query = static::find();
        $query->select($field);
        $asd = $query->where($map);
        $query->orderBy($order);
        $query->andWhere($params);
        if (!empty($indexBy)) {
            $query->indexBy($indexBy);
        }
        if (!empty($distinct)) {
            $query->distinct($distinct);
        }

        $query->asArray();
        $menuList = $query->all();
        return $menuList;
    }

    /**
     * 获取后台菜单 app_name为admin,默认的系统后台
     */
    public static function getAdminNavTree($map = ['app_name' => 'admin', 'status' => 1], $order = ['sort' => SORT_ASC, 'parent_id' => SORT_ASC, 'menu_id' => SORT_ASC])
    {
        $navArr = [];
        $navChild = [];
        $params = [];
        if (!Yii::$app->session->get('isSuperAdmin')) {//检测是否为系统超级管理员
            $auth_urls = Auth::getAuthMenuByUser();
            $params = ['in', 'auth_url', $auth_urls];
        }
        $navList = static::getMenu($map, $order, "*", $params);
        if (!empty($navList)) {
            if (is_array($navList)) {
                $refer = [];
                foreach ($navList as $key => $data) {
                    $navList[$key]['id'] = $data['menu_id'] . 'menu';
                    $navList[$key]['parent_id'] = $data['parent_id'] . 'menu';
                    $param = $data['param'] ? '&' . $data['param'] : '';
                    if ($map['app_name'] == 'admin') {
                        $navList[$key]['url'] = Url::to([$data['controller'] . '/' . $data['action']]) . $param;
                    } else {
                        $navList[$key]['url'] = Url::to(['/' . $data['module'] . '/' . $data['controller'] . '/' . $data['action']]) . $param;
                    }
                    $refer[$data['menu_id'] . 'menu'] = & $navList[$key];
                }
                foreach ($navList as $key => $data) {
                    $parentId = $data['parent_id'];
                    if ($parentId == '0menu') {
                        $navList[$key]['wid'] = $data['menu_id'];
                        unset($navList[$key]['parent_id']);
                        $navArr[$data['id']] = & $navList[$key];
                    } else {
                        if (isset($refer[$parentId])) {
                            $parent = & $refer[$parentId];
                            $parent['items'][] = & $navList[$key];
                        }
                    }
                }
            }
        }
        return $navArr;
    }

    /**
     * 获取所有菜单，树形结构呈现
     *
     * @param char $appName     应用名
     * @param int $curCat       当前选择ID
     * @param int $disabled_id  锁定不可选id,如果多个则以数组形式传递 如[1,2]
     * @return array
     */
    public static function getMenuTree($appName = 'adimin', $curCat = 0, $disabled_id = 0)
    {
        $menu = self::getMenu(['app_name' => $appName], [], 'menu_id,parent_id as pid,name');
        $menuList = [];
        foreach ($menu as $result) {
            $menuList[$result['menu_id']] = $result;
        }
        $str = "<option value='\$menu_id' \$selected \$disabled>\$spacer  \$name</option>";
        $Tree = new Tree($menuList);
        $list = $Tree->get_tree(0, $str, (int) $curCat, $disabled_id);
        return $list;
    }

    /**
     * 获取后台缓存菜单，不存在就新建缓存
     * @return type
     */
    public function getAdminMenu()
    {
        $adminNav = [];
        $user_id = \Yii::$app->user->id;
        if ($user_id) {
            $adminNav = Yii::$app->cache->get('adminCacheMenu_' . $user_id);
            if (empty($adminNav)) {
                static::updateMenuCache();
                $adminNav = Yii::$app->cache->get('adminCacheMenu_' . $user_id);
            }
        }
        return $adminNav;
    }

    /**
     * 更新缓存
     */
    public static function updateMenuCache()
    {
        /* 生成后台菜单缓存 */
        $adminNav = static::getAdminNavTree();
        $user_id = \Yii::$app->user->id;
        if ($user_id) {
            Yii::$app->cache->set('adminCacheMenu_' . $user_id, json_encode($adminNav));
        }
    }

    /**
     * 返回状态列表
     */
    public static function getStatusList()
    {
        return [
            self::STATUS_ENABLE => '显示',
            self::STATUS_DISABLE => '隐藏',
        ];
    }

    /**
     * 返回是否建立权限节点列表
     */
    public static function getIsAuthList()
    {
        return [
            self::ISAUTH_ENABLE => '权限认证+菜单',
            self::ISAUTH_DISABLE => '只作为菜单',
        ];
    }

    /**
     * 返回是否显示范围
     */
    public static function getShowScopeList()
    {
        return [
            self::SHOW_SCOPE => '显示',
            self::HIDDEN_SCOPE => '隐藏',
        ];
    }

    /**
     * 获取应用列表
     */
    public static function getAppList()
    {
        $appData = [
            'home' => [
                'name' => '前台oa系统',
                'value' => 'home',
                'id' => 0,
            ],
            'qcbase' => [
                'name' => '后台基础框架',
                'value' => 'admin',
                'id' => 0,
            ],
        ];
        return $appData;
    }

    public static function getAppName()
    {
        $appArr = self::getAppList();

        foreach ($appArr as $item) {
            $data[$item['value']] = $item['name'];
        }
        return $data;
    }

    public function beforeSave($insert)
    {
        if (parent::beforeSave($insert)) {
            if ($this->is_auth == 1) {
                $this->auth_url = $this->module . '/' . $this->controller . '/' . $this->action;
            } else {
                $this->auth_url = '';
            }
            return TRUE;
        } else {
            return FALSE;
        }
    }

    /**
     * 后置操作更新缓存
     */
    public function afterSave($insert, $changedAttributes)
    {
        parent::afterSave($insert, $changedAttributes);
        /* 生成后台菜单缓存 */
        static::updateMenuCache();
        /* 建立或更新节点 */
        $authUrl = $this->auth_url; //. '/' . $this -> controller . '/' . $this -> action;
        $authDescription = $this->name;
        $name = isset($changedAttributes['auth_url']) ? $changedAttributes['auth_url'] : $this->auth_url;
        $authType = 0;
        if ($this->is_auth == 1) {
            $authType = 1;
        } else {
            $authType = 2;
            $authUrl = $name;
        }
        if ($authType) {
            Auth::authItem($authUrl, $authDescription, $authType, $name);
        }
        if ($insert) {
            $cookies = Yii::$app->response->cookies;
            $cookies->add(new \yii\web\Cookie([
                'name' => 'menuDefault',
                'value' => [
                    'app_name' => $this->app_name,
                    'parent_id' => $this->parent_id,
                ],
            ]));
            if (!empty($this->child_actions)) {
                $this->createChildMenu($this, $this->child_actions);
            }
        }
        //修复菜单
        self::repairTree($this, $insert ? 'insert' : 'update', $changedAttributes);
    }

    /**
     * 删除后更新缓存
     */
    public function afterDelete()
    {
        parent::afterDelete();
        if ($this->is_auth == 1) {
            $authUrl = $this->auth_url; // . '/' . $this -> controller . '/' . $this -> action;
            $authDescription = $this->name;
            /* 删除节点 */
            Auth::authItem($authUrl, $authDescription, 2, $authUrl);
        }
        /* 生成后台菜单缓存 */
        static::updateMenuCache();
        //修复菜单
        self::repairTree($this, 'delete', []);
    }

    /**
     * 获取权限树结构
     *
     * @param type $app_name (应用名)
     * @param type $role (指定角色)
     * @param type $column (是否返回带索引菜单 键名)
     * @return boolean
     */
    public static function getAuthTree($app_name = '', $role = '', $column = '')
    {
        $auth = Yii::$app->authManager;
        $authByRole = [];
        $map = [
            'app_name' => $app_name,
        ];
        $order = [
            'sort' => SORT_ASC,
            'parent_id' => SORT_ASC,
            'menu_id' => SORT_ASC,
        ];
        $field = 'app_name,menu_id,name,parent_id,is_auth,auth_url,module,controller,action,show_scope,remark';
        //获取所有菜单
        $menuList = static::getMenu($map, $order, $field, [], $column);
        //获取所有一级菜单
        $map['parent_id'] = 0;
        $list = static::getMenu($map, $order, $field, [], $column);
        //获取节点菜单
        unset($map['parent_id']);
        $map['is_auth'] = 1;
        $authList = static::getMenu($map, $order, $field, [], $column);
        //获取角色的权限
        if (!empty($role)) {
            $roleAuths = $auth->getPermissionsByRole($role);
        }

        foreach ($authList as $k => $auth) {
            //判断是否有权限
            if (isset($roleAuths) && isset($roleAuths[$auth['auth_url']])) {
                $auth['checked'] = true;
            }
            $topMenu = self::getTopMenu($auth['menu_id'], $menuList);
            $parentMenu = (isset($menuList[$auth['parent_id']])) ? $menuList[$auth['parent_id']] : $topMenu;

            if (!isset($list[$topMenu['menu_id']])) {
                $list[$topMenu['menu_id']] = $topMenu;
            }
            if (!isset($list[$topMenu['menu_id']]['second_menu'][$parentMenu['menu_id']])) {
                $list[$topMenu['menu_id']]['second_menu'][$parentMenu['menu_id']] = $parentMenu;
            }
            $list[$topMenu['menu_id']]['second_menu'][$parentMenu['menu_id']]['third_menu'][$auth['menu_id']] = $auth;
        }

        return $list;
    }

    /**
     * 可同步不添加菜单
     */
    public static function getActionList()
    {
        return [
            'index' => '列表[index]',
            'view' => '查看[view]',
            'create' => '添加[create]',
            'update' => '更新[update]',
            'delete' => '删除[delete]',
            'sort' => '排序[sort]'
        ];
    }

    /**
     * 批量添加子菜单
     * @param type $menuValue    当前添加菜单对象
     * @param type $childActions 要添加子菜单
     */
    public function createChildMenu($menuValue, $childActions = [])
    {
        if (!empty($childActions)) {
            $data = [
                'app_name' => $menuValue->app_name,
                'module' => $menuValue->module,
                'controller' => $menuValue->controller,
                'parent_id' => $menuValue->menu_id,
                'is_auth' => 1
            ];
            $auth_url = $menuValue->module . '/' . $menuValue->controller . '/';
            $authDescription = $menuValue->name;
            foreach ($childActions as $k => $child) {
                $data['action'] = $child;
                $data['auth_url'] = $auth_url . $child;
                $data['status'] = 0;
                switch ($child) {
                    case 'index':
                        $data['name'] = '列表';
//                        $data['status'] = 1;
                        break;
                    case 'view':
                        $data['name'] = '查看';
                        break;
                    case 'create':
                        $data['name'] = '添加';
//                        $data['status'] = 1;
                        break;
                    case 'update':
                        $data['name'] = '更新';
                        break;
                    case 'delete':
                        $data['name'] = '删除';
                        break;
                    case 'sort':
                        $data['name'] = '排序';
                        break;
                }
                $result = Yii::$app->db->createCommand()->insert('{{%menu}}', $data)->execute();
                if ($result !== FALSE) {
                    Auth::authItem($data['auth_url'], $authDescription, 1, $data['auth_url']);
                    //修复菜单
                    self::repairMenu();
                }
            }
        }
    }

    /**
     * 修复节点取得顶级父类
     *
     * @param object $data 任务对象
     */
    public static function getRepairTopParent($data)
    {
        if (empty($data) || $data->parent_id == 0) {
            return $data;
        }
        $parentData = static::findOne($data->parent_id);

        if ($parentData['parent_id']) {
            $parentIds = explode(',', $parentData['parent_ids']);
            $topParentId = $parentIds[1];
            $topParentData = static::findOne($topParentId);
        } else {
            $topParentData = $parentData;
        }
        return $topParentData;
    }

    /**
     * 更新表里的父级ID集，子ID集
     *
     * @param char $tableName 要更新的表名
     */
    public static function repairTree($data, $action, $changedAttributes)
    {
        set_time_limit(0);
        $query = static::find();

        //根据操作的不同取得所有波及到的数据来更新树结构
        if ($action == 'insert' || $action == 'delete') {
            if ($data->parent_id) {
                $topParentData = self::getRepairTopParent($data);
                $inIds = explode(',', $topParentData['child_ids']);
                $inIds[] = $data->menu_id;
                $query->where(['menu_id' => $inIds]);
            } else {
                $query->where(['menu_id' => $data->menu_id]);
            }
        } elseif ($action == 'update') {
            //判断父有没有改变,若未变则返回
            if (!array_key_exists('parent_id', $changedAttributes) || $data->parent_id == $changedAttributes['parent_id']) {
                $topParentData = self::getRepairTopParent($data);
                $inIds = explode(',', $topParentData['child_ids']);
                $query->where(['menu_id' => $inIds]);
            } else {
                //取得原来父的顶级数据
                $oldData = clone $data; //注意！这里的data是对象，必须复制一份而不能赋值
                $oldData['parent_id'] = $changedAttributes['parent_id'];
                $oldTopParentData = self::getRepairTopParent($oldData);
                //取得最新父任务的顶级
                $newTopParentData = self::getRepairTopParent($data);

                //如果顶级相同则取其一
                if ($oldTopParentData['menu_id'] == $newTopParentData['menu_id']) {
                    $inIds = explode(',', $oldTopParentData['child_ids']);
                    $inIds[] = $data->menu_id;
                    $query->where(['menu_id' => $inIds]);
                } else {
                    $oldInIds = explode(',', $oldTopParentData['child_ids']);
                    $newInIds = explode(',', $newTopParentData['child_ids']);
                    $inIds = array_merge($oldInIds, $newInIds);
                    $query->where(['menu_id' => $inIds]);
                }
            }
        }
        self::$list = $query
            ->asArray()
            ->indexBy('menu_id')
            ->orderBy(['menu_id' => SORT_ASC])
            ->all();

        if (is_array(self::$list)) {
            $command = Yii::$app->db->createCommand();

            //这里要先更新父级ID集,然后在更新子集，因为子集的选择需要依赖父级字段，且会有顺序问题，
            //而顺序在许多情况下是不可控的，所以只能等待所有父级全部获取成功后才去更新子级
            foreach (self::$list as $id => $value) {
                self::$list[$id]['parent_ids'] = $parent_ids = self::getParentids($id);
            }
            foreach (self::$list as $id => $value) {
                $parent_ids = self::$list[$id]['parent_ids'];
                self::$list[$id]['child_ids'] = $child_ids = self::getChildids($id);

                $command->update(self::tableName(), ['parent_ids' => $parent_ids, 'child_ids' => $child_ids], 'menu_id = :id', [':id' => $id])->execute();
            }
        }
    }

    /**
     * 一次修复所有菜单子父关系
     * （预留）
     *
     * @author HuLimin <blog.u-bo.com>
     */
    public static function repairMenu()
    {
        set_time_limit(0);
        $lists = [];
        $lists = self::find()->indexBy('menu_id')->all();
        if (is_array($lists)) {
            $command = Yii::$app->db->createCommand();
            //先更新父级ID集
            foreach ($lists as $id => $vo) {
                $lists[$id]['parent_ids'] = $parent_ids = self::getParentids($id);
                $command->update('{{%menu}}', ['parent_ids' => $parent_ids], 'menu_id=:id', [':id' => $id])->execute();
            }
            //通过更新后的父级ID集更新子ID集
            foreach ($lists as $id => $item) {
                $lists[$id]['child_ids'] = $child_ids = self::getChildids($id);
                $command->update('{{%menu}}', ['child_ids' => $child_ids], 'menu_id=:id', [':id' => $id])->execute();
            }
        }
        return true;
    }

    /**
     * 获取父级ID集
     * 
     * @param  integer $id          表ID
     * @param  string  $arrparentid 父级ID集
     * @return string  $arrparentid 父级ID集
     */
    public static function getParentids($id = 0, $arrparentid = '')
    {
        $lists = [];
        $lists = self::find()->indexBy('menu_id')->all();
        if (!is_array($lists) || !isset($lists[$id])) return false;
        $parentid = $lists[$id]['parent_id'];
        $arrparentid = $arrparentid ? $parentid . ',' . $arrparentid : $parentid;
        if ($parentid) {
            $arrparentid = self::getParentids($parentid, $arrparentid);
        } else {
            $lists[$id]['parent_ids'] = $arrparentid;
        }
        return $arrparentid;
    }

    /**
     * 获取子ID集
     * 
     * @param  integer $id         表ID
     * @return string  $arrchildid 子ID集
     */
    public static function getChildids($id = 0)
    {
        $lists = [];
        $lists = self::find()->indexBy('menu_id')->all();
        $arrchildid = $id;
        if (is_array($lists)) {
            foreach ($lists as $deptid => $item) {
                if ($item['parent_id'] && $id != $deptid) {
                    $arrparentids = explode(',', $item['parent_ids']);
                    if (in_array($id, $arrparentids)) {
                        $arrchildid .= ',' . $deptid;
                    }
                }
            }
        }
        return $arrchildid;
    }

    /**
     * 设置缓存
     * @author legendjw
     */
    public static function setCache()
    {
        $cache = Yii::$app->cache;

        //设置菜单缓存
        $menuListForLog = static::getMenu(NULL, ['sort' => SORT_ASC, 'menu_id' => SORT_ASC], "menu_id,app_name,name,parent_id,module,controller,action,auth_url", NULL, 'auth_url', 'auth_url');
        $cache->set('menu_list_for_log', $menuListForLog);
    }

    /**
     * 递归获取顶级菜单
     *
     * @param int $menuId 要获取的菜单的id
     * @param array $menuList 所有的菜单数组,如果存在则不从数据库中获取菜单
     */
    public static function getTopMenu($menuId, $menuList = [])
    {
        $menu = ($menuList !== []) ? ArrayHelper::getValue($menuList, $menuId) : self::findOne($menuId);
        if ($menu === null) {
            throw new ErrorException('菜单id为' . $menuId . '的菜单不存在');
        }

        if ($menu['parent_id'] == 0) {
            return $menu;
        } else {
            return self::getTopMenu($menu['parent_id'], $menuList);
        }
    }
}