luyadev/luya-module-admin

View on GitHub
src/components/AdminMenu.php

Summary

Maintainability
D
2 days
Test Coverage
B
84%
<?php

namespace luya\admin\components;

use luya\Exception;
use luya\helpers\ArrayHelper;
use Yii;

/**
 * Admin Menu Data.
 *
 * Collect informations from the menu data based on the admin modules in order to generate the different admin menu levels and containers.
 *
 * @property array $menu Returns an array with all menu elements.
 * @property integer $userId Returns the current admin user id.
 * @property array $adminModuleMenus An array with \luya\admin\components\AdminMenuBuilderInterface objects.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class AdminMenu extends \yii\base\Component
{
    /**
     * Get current admin user id.
     *
     * @return integer
     */
    public function getUserId()
    {
        return Yii::$app->adminuser->getId();
    }

    /**
     * Get the module menus.
     *
     * @return \luya\admin\components\AdminMenuBuilderInterface An array with AdminMenuBuilderInterface objects.
     */
    public function getAdminModuleMenus()
    {
        return Yii::$app->getAdminModulesMenus();
    }

    private $_menu;

    /**
     * Return the menu array entries from all registered admin modules.
     *
     * Response array is organized as follow:
     *
     * + Node (Root level menu)
     *   + Group
     *      + Items
     *
     * Example array response:
     *
     * ```php
     * Array (
     * [id] => 1, // auto assigned
     * [moduleId] => admin
     * [template] =>
     * [routing] => default
     * [alias] => menu_node_system
     * [icon] => layers
     * [permissionRoute] =>
     * [permissionIsRoute] =>
     * [searchModelClass] =>
     * [groups] => Array ( // Optional
     *        [menu_group_access] => Array  (
     *               [name] => menu_group_access
     *               [items] => Array  (
     *                       [0] => Array (
     *                               [alias] => menu_access_item_user
     *                               [route] => admin/user/index
     *                               [icon] => person
     *                               [permissionApiEndpoint] => api-admin-user
     *                               [permissionIsRoute] =>
     *                               [permissionIsApi] => 1
     *                               [searchModelClass] =>
     *                               [options] => Array ()
     *                           )
     *                       [1] => Array  (
     *                               [alias] => menu_access_item_group
     *                               [route] => admin/group/index
     *                               [icon] => group
     *                               [permissionApiEndpoint] => api-admin-group
     *                               [permissionIsRoute] =>
     *                               [permissionIsApi] => 1
     *                               [searchModelClass] =>
     *                               [options] => Array ( )
     *                           )
     *                   )
     *           )
     * )
     * ```
     *
     * @return array
     */
    public function getMenu()
    {
        if ($this->_menu !== null) {
            return $this->_menu;
        }

        $menu = [];
        foreach ($this->getAdminModuleMenus() as $menuBuilder) {
            // removed legacy support, menu must be instance of AdminmenuBuilderInterface
            if (!$menuBuilder instanceof AdminMenuBuilderInterface) {
                throw new Exception("admin menu must be instance of AdminMenuBuilderInterface");
            }
            // call the interface menu method returns the array for the given Module.
            $data = $menuBuilder->menu();
            $menu = ArrayHelper::merge($menu, $data);
        }

        $this->_menu = $menu;

        return $menu;
    }

    /**
     * Get the node for a given array key.
     *
     * @param integer $id
     * @return boolean|array
     */
    public function getNodeData($id)
    {
        if (!isset($this->getMenu()[$id])) {
            return false;
        }

        return $this->getMenu()[$id];
    }

    private $_modules;

    /**
     * Returns an Array with modules and checks the permission for those.
     *
     * Example Response:
     *
     * ```php
     * Array (
     * 'moduleId' => 'admin',
     * 'id' => 1,
     * 'template' => $item['template'],
     * 'routing' => $item['routing'],
     * 'alias' => $alias,
     * 'icon' => $item['icon'],
     * 'searchModelClass' => $item['searchModelClass'],
     * )
     * ```
     *
     * @throws Exception
     * @return array Returns an array with all modules you have permission for.
     */
    public function getModules()
    {
        if ($this->_modules !== null) {
            return $this->_modules;
        }

        $responseData = [];
        foreach ($this->getMenu() as $item) {
            // check if this is an entrie with a permission
            if ($item['permissionIsRoute']) {
                // verify if the permission is provided for this user:
                // if the permission is granted will write in $responseData,
                // if not we continue;
                if (!Yii::$app->auth->matchRoute($this->getUserId(), $item['permissionRoute'])) {
                    continue;
                }
            }

            $authIds = [];
            // this item does have groups
            if (isset($item['groups'])) {
                $permissionGranted = false;

                // see if the groups has items
                foreach ($item['groups'] as $groupName => $groupItem) {
                    if ((is_countable($groupItem['items']) ? count($groupItem['items']) : 0) > 0) {
                        foreach ($groupItem['items'] as $groupItemEntry) {
                            if ($groupItemEntry['permissionIsRoute']) {
                                // when true, set permissionGranted to true
                                if (($id = Yii::$app->auth->matchRoute($this->getUserId(), $groupItemEntry['route']))) {
                                    $authIds[] = $id;
                                    $permissionGranted = true;
                                }
                            } elseif ($groupItemEntry['permissionIsApi']) {
                                // when true, set permissionGranted to true
                                if (($id = Yii::$app->auth->matchApi($this->getUserId(), $groupItemEntry['permissionApiEndpoint']))) {
                                    $authIds[] = $id;
                                    $permissionGranted = true;
                                }
                            } else {
                                throw new Exception('Menu item detected without permission entry');
                            }
                        }
                    }
                }

                // skip menu stack for this item
                if (!$permissionGranted) {
                    continue;
                }
            }

            try {
                // check if a translation exists, otherwise use alias instead.
                $alias = Yii::t($item['moduleId'], $item['alias'], [], Yii::$app->language);
            } catch (\Exception $err) {
                $alias = $item['alias'];
            }

            // ok we have passed all the tests, lets make an entry
            $responseData[] = [
                'moduleId' => $item['moduleId'],
                'id' => $item['id'],
                'authIds' => $authIds,
                'template' => $item['template'],
                'routing' => $item['routing'],
                'alias' => $alias,
                'icon' => $item['icon'],
                'searchModelClass' => $item['searchModelClass'],
            ];
        }

        $this->_modules = $responseData;

        return $responseData;
    }


    private array $_nodeItems = [];

    /**
     * Returns the node with a groups array where each groups contains an items array with the item.
     *
     * @param integer $nodeId
     * @throws \Exception
     * @return array If there are not groups, or not items for a group an empty array will be returned.
     */
    public function getModuleItems($nodeId)
    {
        if (isset($this->_nodeItems[$nodeId])) {
            return $this->_nodeItems[$nodeId];
        }

        $data = $this->getNodeData($nodeId);

        if (isset($data['groups'])) {
            foreach ($data['groups'] as $groupName => $groupItem) {
                // translate the group names
                try {
                    $data['groups'][$groupName]['name'] = Yii::t($data['moduleId'], $groupItem['name'], [], Yii::$app->language);
                } catch (\Exception $e) {
                    // do nothing and keep the current name inside the array
                }
                foreach ($groupItem['items'] as $groupItemKey => $groupItemEntry) {
                    if ($groupItemEntry['permissionIsRoute']) {
                        $id = Yii::$app->auth->matchRoute($this->getUserId(), $groupItemEntry['route']);
                        if (!$id) {
                            unset($data['groups'][$groupName]['items'][$groupItemKey]);
                            continue;
                        }
                    } elseif ($groupItemEntry['permissionIsApi']) {
                        $id = Yii::$app->auth->matchApi($this->getUserId(), $groupItemEntry['permissionApiEndpoint']);
                        if (!$id) {
                            unset($data['groups'][$groupName]['items'][$groupItemKey]);
                            continue;
                        }
                    } else {
                        throw new Exception('Menu item detected without permission entry');
                    }
                    try {
                        $alias = Yii::t($data['moduleId'], $data['groups'][$groupName]['items'][$groupItemKey]['alias'], [], Yii::$app->language);
                    } catch (\Exception $err) {
                        $alias = $data['groups'][$groupName]['items'][$groupItemKey]['alias'];
                    }

                    // if a pool is available, the route will be modified by appending the pool param
                    $pool = AdminMenuBuilder::getOptionValue($groupItemEntry, 'pool', null);
                    if ($pool) {
                        $data['groups'][$groupName]['items'][$groupItemKey]['route'] = $data['groups'][$groupName]['items'][$groupItemKey]['route'] . '?pool='.$pool;
                    }

                    $data['groups'][$groupName]['items'][$groupItemKey]['hiddenInMenu'] = AdminMenuBuilder::getOptionValue($groupItemEntry, 'hiddenInMenu', false);
                    $data['groups'][$groupName]['items'][$groupItemKey]['pool'] = AdminMenuBuilder::getOptionValue($groupItemEntry, 'pool', null);
                    $data['groups'][$groupName]['items'][$groupItemKey]['alias'] = $alias;
                    $data['groups'][$groupName]['items'][$groupItemKey]['authId'] = $id;
                }

                // if there are no items for this group, unset the group from the data array
                if ((is_countable($data['groups'][$groupName]['items']) ? count($data['groups'][$groupName]['items']) : 0) == 0) {
                    unset($data['groups'][$groupName]);
                }
            }

            // if there are no groups reset the array to an empty array
            if (empty($data['groups'])) {
                $data = [];
            }
        } else {
            // as we want to catch all groups and the items of the groups, if the group has no items reset the array to an empty arrray
            $data = [];
        }

        $this->_nodeItems[$nodeId] = $data;

        return $data;
    }

    private $_items;

    /**
     * Get all items for all nodes.
     *
     * @return array
     */
    public function getItems()
    {
        if ($this->_items !== null) {
            return $this->_items;
        }

        $data = [];
        foreach ($this->getModules() as $node) {
            foreach (ArrayHelper::getValue($this->getModuleItems($node['id']), 'groups', []) as $groupValue) {
                foreach ($groupValue['items'] as $array) {
                    $array['module'] = $node;
                    $data[] = $array;
                }
            }
        }

        $this->_items = $data;

        return $data;
    }

    /**
     * Return all informations about a menu point based on the api endpoint name.
     *
     * @param string $api The Api Endpoint
     * @return array|boolean
     */
    public function getApiDetail($api, $pool = null)
    {
        $items = $this->getItems();

        if ($pool) {
            $items = ArrayHelper::searchColumns($items, 'pool', $pool);
        }
        $items = array_values($items); // reset keys to fix isset error
        return ArrayHelper::searchColumn($items, 'permissionApiEndpoint', $api);
    }
}