<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magento.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magento.com for more information.
 *
 * @category    Mage
 * @package     Mage_Admin
 * @copyright  Copyright (c) 2006-2020 Magento, Inc. (http://www.magento.com)
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */

/**
 * Admin Roles Model
 *
 * @method Mage_Admin_Model_Resource_Roles _getResource()
 * @method Mage_Admin_Model_Resource_Roles getResource()
 * @method Mage_Admin_Model_Resource_Roles_Collection getResourceCollection()
 *
 * @method int getParentId()
 * @method $this setParentId(int $value)
 * @method int getTreeLevel()
 * @method $this setTreeLevel(int $value)
 * @method int getSortOrder()
 * @method $this setSortOrder(int $value)
 * @method string getRoleType()
 * @method $this setRoleType(string $value)
 * @method int getUserId()
 * @method $this setUserId(int $value)
 * @method string getRoleName()
 * @method $this setRoleName(string $value)
 * @method string getName()
 * @method int getPid()
 *
 * @category    Mage
 * @package     Mage_Admin
 * @author      Magento Core Team <core@magentocommerce.com>
 */
class Mage_Admin_Model_Roles extends Mage_Core_Model_Abstract
{
    /**
     * @var string
     */
    protected $_eventPrefix = 'admin_roles';

    /**
     * Constructor
     */
    protected function _construct()
    {
        $this->_init('admin/roles');
    }

    /**
     * Update object into database
     *
     * @return $this
     */
    public function update()
    {
        $this->getResource()->update($this);
        return $this;
    }

    /**
     * Retrieve users collection
     *
     * @return Mage_Admin_Model_Resource_Roles_User_Collection
     */
    public function getUsersCollection()
    {
        return Mage::getResourceModel('admin/roles_user_collection');
    }

    /**
     * Return tree of acl resources
     *
     * @param list<string> $selectedItems
     * @return array
     */
    public function getResourcesTree(array $selectedItems)
    {
        return $this->_buildResourcesArrayRaw(
            $this->getAclResources(),
            "adminhtml",
            $selectedItems,
            filterModules: false,
            translate: true,
        );
    }

    /**
     * @param list<string> $selectedItems
     * @return array
     */
    public function getResourcesTreeApi(array $selectedItems)
    {
        return $this->_buildResourcesArrayRaw(
            $this->getAclResources(),
            "adminhtml",
            $selectedItems,
            filterModules: true,
            translate: false,
        );
    }

    /**
     * Return list of acl resources
     *
     * @return list<string>
     */
    public function getResourcesList()
    {
        return $this->_buildResourcesArrayList($this->getAclResources(), "adminhtml");
    }

    /**
     * Return list of acl resources in 2D format
     *
     * @return Array<string, array{name:string, level:int}>
     */
    public function getResourcesList2D()
    {
        return $this->_buildResourcesArray2D($this->getAclResources(), "adminhtml");
    }

    /**
     * Return users for role
     *
     * @return array|false
     */
    public function getRoleUsers()
    {
        return $this->getResource()->getRoleUsers($this);
    }

    private function getAclResources(): Varien_Simplexml_Element {
        $resource = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode('acl/resources');

        assert($resource !== null);

        return $resource;
    }

    /**
     * @param list<string> $path
     * @return Array<string, array{name:string, level:int}>
     */
    private function _buildResourcesArrayList(
        Varien_Simplexml_Element $node,
        string $module,
        array $path = []
    ): array {
        $result = [];

        foreach($node->children() as $key => $child) {
            if($child->disabled?->asBool()) {
                continue;
            }

            if( ! in_array($key, self::DATA_NODES)) {
                $childPath = [...$path, $key];
                $resourceName = implode("/", $childPath);
                /** @var string */
                $childModule = empty($child['module']) ? $module : $child['module'];
                $result[$resourceName] = [
                    'name'  => Mage::helper($childModule)->__((string)$child->title),
                    'level' => count($path),
                ];

                $result = array_merge($result, $this->_buildResourcesArrayList(
                    isset($child->children) ? $child->children : $child,
                    $childModule,
                    $childPath
                ));
            }
        }

        return $result;
    }

    /**
     * @param list<string> $path
     * @return list<string>
     */
    private function _buildResourcesArray2D(
        Varien_Simplexml_Element $node,
        array $path = []
    ): array {
        $result = [];

        foreach($node->children() as $key => $child) {
            if($child->disabled?->asBool()) {
                continue;
            }

            if( ! in_array($key, self::DATA_NODES)) {
                $childPath = [...$path, $key];
                $result[] = implode("/", $childPath);
                $result = array_merge($result, $this->_buildResourcesArray2D(
                    isset($child->children) ? $child->children : $child,
                    $childPath
                ));
            }
        }

        return $result;
    }

    const DATA_NODES = ['title', 'sort_order', 'children', 'disabled'];

    /**
     * @param list<string> $selectedItems
     * @param list<string> $path
     * @return list<array{text:string, sort_order:int, id:string, checked:bool, module_c:string, children:list}>
     */
    private function _buildResourcesArrayRaw(
        Varien_Simplexml_Element $node,
        string $module,
        array $selectedItems,
        bool $filterModules,
        bool $translate,
        array $path = [],
    ): array {
        $items = [];

        foreach($node->children() as $key => $child) {
            if($child->disabled?->asBool()) {
                continue;
            }

            if( ! in_array($key, self::DATA_NODES) &&
                ( ! $filterModules || ! empty($child["module"]))
            ) {
                $childPath = [...$path, $key];
                $resourceName = implode("/", $childPath);
                /** @var string */
                $childModule = empty($child['module']) ? $module : $child['module'];
                $children = $this->_buildResourcesArrayRaw(
                    // Flatten children element
                    node: isset($child->children) ? $child->children : $child,
                    module: $childModule,
                    selectedItems: $selectedItems,
                    filterModules: $filterModules,
                    translate: $translate,
                    path: $childPath
                );

                usort($children, fn($a, $b): int => $a['sort_order'] < $b['sort_order'] ? -1 : ($a['sort_order'] > $b['sort_order'] ? 1 : 0));

                $items[] = [
                    "text" => $translate ?
                        Mage::helper("adminhtml")->__((string)$child->title) :
                        (string)$child->title,
                    "sort_order" => $child->sort_order?->asInt() ?? 0,
                    "id" => $resourceName,
                    "checked" => in_array($resourceName, $selectedItems),
                    "module_c" => $childModule,
                    "level" => count($childPath),
                    "children" => $children,
                ];
            }
        }

        return $items;
    }
}
