luyadev/luya-module-cms

View on GitHub
src/admin/helpers/MenuHelper.php

Summary

Maintainability
C
1 day
Test Coverage
C
78%
<?php

namespace luya\cms\admin\helpers;

use luya\admin\models\Group;
use luya\admin\models\User;
use luya\cms\models\Nav;
use luya\helpers\ArrayHelper;
use Yii;
use yii\db\Query;
use yii\helpers\Json;

/**
 * Menu Helper to collect Data used in Administration areas.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class MenuHelper
{
    private static $items;

    /**
     * Get all nav data entries with corresponding item content
     *
     * @return array
     */
    public static function getItems()
    {
        if (self::$items === null) {
            $items = Nav::find()
                ->select(['cms_nav.id', 'nav_item_id' => 'cms_nav_item.id', 'nav_container_id', 'parent_nav_id', 'is_hidden', 'layout_file', 'is_offline', 'is_draft', 'is_home', 'cms_nav_item.title'])
                ->leftJoin('cms_nav_item', 'cms_nav.id=cms_nav_item.nav_id')
                ->with(['parents'])
                ->orderBy(['sort_index' => SORT_ASC])
                ->where([
                    'cms_nav_item.lang_id' => Yii::$app->adminLanguage->defaultLanguage['id'],
                    'cms_nav.is_deleted' => false,
                    'cms_nav.is_draft' => false,
                ])
                ->asArray()
                ->all();
            self::loadInheritanceData(0);

            $data = [];

            foreach ($items as $key => $item) {
                $item['is_draft'] = (int) $item['is_draft'];
                $item['is_hidden'] = (int) $item['is_hidden'];
                $item['is_home'] = (int) $item['is_home'];
                $item['is_offline'] = (int) $item['is_offline'];
                $item['is_editable'] = (int) Yii::$app->adminuser->canRoute('cmsadmin/page/update');
                $item['toggle_open'] = (int) Yii::$app->adminuser->identity->setting->get('tree.'.$item['id']);
                $item['has_children'] = empty($item['parents']) ? 0 : count($item['parents']); //(new Query())->from('cms_nav')->select(['id'])->where(['parent_nav_id' => $item['id']])->count();
                // the user have "page edit" permission, now we can check if the this group has more fined tuned permisionss from the
                // cms_nav_permissions table or not
                if ($item['is_editable']) {
                    $permitted = false;
                    // check permissions for all groups, if alreay permited skip
                    foreach (Yii::$app->adminuser->identity->groups as $group) {
                        if ($permitted) {
                            continue;
                        }

                        $permitted = self::navGroupPermission($item['id'], $group->id);
                    }

                    // if not yet permitted, check in inheritance table.
                    if (!$permitted) {
                        $value = self::$_inheritData[$item['id']] ?? false;
                        if ($value === true) {
                            $permitted = true;
                        }
                    }
                    $item['is_editable'] = (int) $permitted;
                }
                $data[$key] = $item;
            }
            self::$items = $data;
        }

        return self::$items;
    }

    private static $_navItems;

    /**
     * Get an array with all nav items.
     *
     * @return array
     */
    private static function getNavItems()
    {
        if (self::$_navItems === null) {
            $items = Nav::find()->select(['sort_index', 'id', 'parent_nav_id', 'is_deleted'])->where(['is_deleted' => false])->orderBy(['sort_index' => SORT_ASC])->asArray()->all();
            return self::$_navItems = ArrayHelper::index($items, null, 'parent_nav_id');
        }

        return self::$_navItems;
    }

    private static array $_inheritData = [];

    /**
     * Find nav_id inheritances
     *
     * + Get all cms_nav items where is deleted 0 and sort_asc
     * + foreach items
     * + foreach all user groups for this item to check if an inheritance nod exists for this nav_item (self::navGroupInheritanceNode)
     * + Set the interanl check to false, if inherit or internal check is true, set value into $data factory
     * + proceed nodes of the current item with the information form $data factory as inheritation info.
     *
     * @param integer $parentNavId
     * @param string $fromInheritNode
     */
    private static function loadInheritanceData($parentNavId = 0, $fromInheritNode = false)
    {
        // get items from singleton object
        $items = self::getNavItems()[$parentNavId] ?? [];
        foreach ($items as $item) {
            $internalCheck = false;
            foreach (Yii::$app->adminuser->identity->groups as $group) {
                if ($internalCheck) {
                    continue;
                }
                $internalCheck = self::navGroupInheritanceNode($item['id'], $group);
            }
            if (!array_key_exists($item['id'], self::$_inheritData)) {
                if ($fromInheritNode || $internalCheck) {
                    self::$_inheritData[$item['id']] = true;
                } else {
                    self::$_inheritData[$item['id']] = false;
                }
            }

            self::loadInheritanceData($item['id'], self::$_inheritData[$item['id']]);
        }
    }

    private static $_cmsPermissionData;

    /**
     * Get an array with all cms permissions data
     *
     * @return array
     */
    private static function getCmsPermissionData()
    {
        if (self::$_cmsPermissionData === null) {
            self::$_cmsPermissionData = ArrayHelper::index((new Query())->select("*")->from("cms_nav_permission")->all(), null, 'group_id');
        }

        return self::$_cmsPermissionData;
    }

    /**
     * Check the inhertiance for a given navigation and group.
     *
     * @param integer $navId
     * @return boolean
     */
    public static function navGroupInheritanceNode($navId, Group $group)
    {
        // default defintion is false
        $definition = false;
        // see if permission data for group exists, foreach items and set if match
        if (isset(self::getCmsPermissionData()[$group->id])) {
            foreach (self::getCmsPermissionData()[$group->id] as $item) {
                if ($item['nav_id'] == $navId) {
                    $definition = $item['inheritance'];
                }
            }
        }
        if ($definition) {
            return (bool) $definition;
        }

        return false;
    }

    private static $_navGroupPermissions;

    /**
     * An array with permissions
     *
     * @return array
     */
    private static function getNavGroupPermissions()
    {
        if (self::$_navGroupPermissions === null) {
            self::$_navGroupPermissions = ArrayHelper::index((new Query())->select(['group_id', 'nav_id'])->from("cms_nav_permission")->all(), null, 'group_id');
        }

        return self::$_navGroupPermissions;
    }

    /**
     * Get the permissions for a certain group and navigation.
     *
     * @param integer $navId
     * @param interger $groupId
     * @return boolean
     */
    public static function navGroupPermission($navId, $groupId)
    {
        // If the group does not exists, it means no permission restrictions are made for this group.
        $definitions = self::getNavGroupPermissions()[$groupId] ?? [];

        // the group has no permission defined, this means he can access ALL cms pages
        if ((is_countable($definitions) ? count($definitions) : 0) == 0) {
            return true;
        }

        // check if the nav id is defined in group
        foreach ($definitions as $permission) {
            if ($navId == $permission['nav_id']) {
                return true;
            }
        }

        return false;
    }

    private static $containers;

    /**
     * Get all cms containers
     *
     * @return array
     */
    public static function getContainers()
    {
        if (self::$containers === null) {
            self::$containers = (new Query())->select(['cms_nav_container.id', 'name' => 'cms_nav_container.name', 'website_name' => 'cms_website.name', 'alias', 'website_id'])
                ->from('cms_nav_container')
                ->innerJoin('cms_website', 'website_id = cms_website.id')
                ->where(['cms_nav_container.is_deleted' => false])
                ->orderBy(['cms_website.name' => 'ASC', 'cms_nav_container.name' => 'ASC'])
                ->all();
        }

        return self::$containers;
    }

    private static $websites;

    /**
     * Get all cms websites
     *
     * @return array
     *
     * @since 4.0.0
     */
    public static function getWebsites()
    {
        if (self::$websites === null) {
            $websites = (new Query())
                ->select([
                    'cms_website.id',
                    'cms_website.name',
                    'cms_website.host',
                    'cms_website.is_default',
                    'default_container_id' => 'MIN(cms_nav_container.id)',
                    'group_ids',
                    'user_ids'
                ])
                ->from('cms_website')
                ->leftJoin('cms_nav_container', 'website_id = cms_website.id')
                ->where(['cms_website.is_active' => true, 'cms_website.is_deleted' => false])
                ->groupBy('cms_website.id')
                ->all();

            foreach ($websites as $websiteIndex => $website) {
                if (!self::checkUserWebsitePermissions($website)) {
                    unset($websites[$websiteIndex]);
                }
            }

            self::$websites = array_values($websites);
        }

        return self::$websites;
    }

    /**
     * Check that the current user is allowed to access to the given website data.
     *
     * @param array $website Array with website data. Have to provide `user_ids` and `group_ids` in json string.
     *
     * @return bool
     */
    public static function checkUserWebsitePermissions(array $website)
    {
        /** @var User $user */
        $user = Yii::$app->adminuser->identity;
        $userGroupIds = ArrayHelper::getColumn($user->groups, 'id');

        $users = Json::decode($website['user_ids']) ?? [];
        foreach ($users as $item) {
            if ($item['value'] === 0 || $item['value'] == $user->id) {
                return true;
            }
        }

        $groups = Json::decode($website['group_ids']) ?? [];
        foreach ($groups as $item) {
            if ($item['value'] === 0 || in_array($item['value'], $userGroupIds)) {
                return true;
            }
        }

        return false;
    }

    private static $drafts;

    /**
     * Get all drafts nav items
     *
     * @return array
     */
    public static function getDrafts()
    {
        if (self::$drafts === null) {
            self::$drafts = (new Query())
            ->select(['cms_nav.id', 'nav_container_id', 'parent_nav_id', 'is_hidden', 'is_offline', 'is_draft', 'is_home', 'cms_nav_item.title'])
            ->from('cms_nav')
            ->leftJoin('cms_nav_item', 'cms_nav.id=cms_nav_item.nav_id')
            ->orderBy('cms_nav.sort_index ASC')
            ->where(['cms_nav_item.lang_id' => Yii::$app->adminLanguage->defaultLanguage['id'], 'cms_nav.is_deleted' => false, 'cms_nav.is_draft' => true])
            ->all();
        }

        return self::$drafts;
    }
}