%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /backups/router/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/
Upload File :
Create Path :
Current File : //backups/router/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/MenuSystem.php

<?php

/*
 * Copyright (C) 2015-2020 Franco Fichtner <franco@opnsense.org>
 * Copyright (C) 2015 Deciso B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace OPNsense\Base\Menu;

use OPNsense\Core\AppConfig;
use OPNsense\Core\Config;

/**
 * Class MenuSystem
 * @package OPNsense\Base\Menu
 */
class MenuSystem
{
    /**
     * @var null|MenuItem root node
     */
    private $root = null;

    /**
     * @var string location to store merged menu xml
     */
    private $menuCacheFilename = null;

    /**
     * @var int time to live for merged menu xml
     */
    private $menuCacheTTL = 3600;

    /**
     * add menu structure to root
     * @param string $filename menu xml filename
     * @return \SimpleXMLElement
     * @throws MenuInitException unloadable menu xml
     */
    private function addXML($filename)
    {
        // load and validate menu xml
        if (!file_exists($filename)) {
            throw new MenuInitException('Menu xml ' . $filename . ' missing');
        }
        $menuXml = simplexml_load_file($filename);
        if ($menuXml === false) {
            throw new MenuInitException('Menu xml ' . $filename . ' not valid');
        }
        if ($menuXml->getName() != "menu") {
            throw new MenuInitException('Menu xml ' . $filename . ' seems to be of wrong type');
        }

        return $menuXml;
    }

    /**
     * append menu item to existing root
     * @param string $root xpath expression
     * @param string $id item if (tag name)
     * @param array $properties properties
     * @return null|MenuItem
     */
    public function appendItem($root, $id, $properties)
    {
        $node = $this->root;
        foreach (explode(".", $root) as $key) {
            $node = $node->findNodeById($key);
            if ($node == null) {
                return null;
            }
        }

        return $node->append($id, $properties);
    }

    /**
     * invalidate cache, removes cache file from disk if available, which forces the next request to persist() again
     */
    public function invalidateCache()
    {
        @unlink($this->menuCacheFilename);
    }

    /**
     * Load and persist Menu configuration to disk.
     * @param bool $nowait when the cache is locked, skip waiting for it to become available.
     * @return SimpleXMLElement
     */
    public function persist($nowait = true)
    {
        // fetch our model locations
        $appconfig = new AppConfig();
        if (!empty($appconfig->application->modelsDir)) {
            $modelDirs = $appconfig->application->modelsDir;
            if (!is_array($modelDirs) && !is_object($modelDirs)) {
                $modelDirs = array($modelDirs);
            }
        }

        // collect all XML menu definitions into a single file
        $menuXml = new \DOMDocument('1.0');
        $root = $menuXml->createElement('menu');
        $menuXml->appendChild($root);
        // crawl all vendors and modules and add menu definitions
        foreach ($modelDirs as $modelDir) {
            foreach (glob(preg_replace('#/+#', '/', "{$modelDir}/*")) as $vendor) {
                foreach (glob($vendor . '/*') as $module) {
                    $menu_cfg_xml = $module . '/Menu/Menu.xml';
                    if (file_exists($menu_cfg_xml)) {
                        try {
                            $domNode = dom_import_simplexml($this->addXML($menu_cfg_xml));
                            $domNode = $root->ownerDocument->importNode($domNode, true);
                            $root->appendChild($domNode);
                        } catch (MenuInitException $e) {
                            error_log($e);
                        }
                    }
                }
            }
        }
        // flush to disk
        $fp = fopen($this->menuCacheFilename, file_exists($this->menuCacheFilename) ? "r+" : "w+");
        $lockMode = $nowait ? LOCK_EX | LOCK_NB : LOCK_EX;
        if (flock($fp, $lockMode)) {
            ftruncate($fp, 0);
            fwrite($fp, $menuXml->saveXML());
            fflush($fp);
            flock($fp, LOCK_UN);
            fclose($fp);
            chmod($this->menuCacheFilename, 0660);
        }
        // return generated xml
        return simplexml_import_dom($root);
    }

    /**
     * check if stored menu's are expired
     * @return bool is expired
     */
    public function isExpired()
    {
        if (file_exists($this->menuCacheFilename)) {
            $fstat = stat($this->menuCacheFilename);
            return $this->menuCacheTTL < (time() - $fstat['mtime']);
        }
        return true;
    }

    /**
     * construct a new menu
     * @throws MenuInitException
     */
    public function __construct()
    {
        // set cache location
        $this->menuCacheFilename = sys_get_temp_dir() . "/opnsense_menu_cache.xml";

        // load menu xml's
        $menuxml = null;
        if (!$this->isExpired()) {
            $menuxml = @simplexml_load_file($this->menuCacheFilename);
        }
        if ($menuxml == null) {
            $menuxml = $this->persist();
        }

        // load menu xml's
        $this->root = new MenuItem("root");
        foreach ($menuxml as $menu) {
            foreach ($menu as $node) {
                $this->root->addXmlNode($node);
            }
        }

        $config = Config::getInstance()->object();

        // collect interfaces for dynamic (interface) menu tabs...
        $iftargets = ['if' => [], 'gr' => [], 'wl' => [], 'fw' => [], 'dhcp4' => [], 'dhcp6' => []];
        $ifgroups = [];
        $ifgroups_seq = [];

        if ($config->interfaces->count() > 0) {
            if ($config->ifgroups->count() > 0) {
                foreach ($config->ifgroups->children() as $key => $node) {
                    if (empty($node->members) || !empty($node->nogroup)) {
                        continue;
                    }
                    if (!empty((string)$node->sequence)) {
                        $ifgroups_seq[(string)$node->ifname] = (int)((string)$node->sequence);
                    }
                    /* we need both if and gr reference */
                    $iftargets['if'][(string)$node->ifname] = (string)$node->ifname;
                    $iftargets['gr'][(string)$node->ifname] = (string)$node->ifname;
                    foreach (preg_split('/[ |,]+/', (string)$node->members) as $member) {
                        if (!array_key_exists($member, $ifgroups)) {
                            $ifgroups[$member] = [];
                        }
                        array_push($ifgroups[$member], (string)$node->ifname);
                    }
                }
            }

            foreach ($config->interfaces->children() as $key => $node) {
                // Interfaces tab
                if (empty($node->virtual)) {
                    $iftargets['if'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
                }
                // Wireless status tab
                if (isset($node->wireless)) {
                    $iftargets['wl'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
                }
                // "Firewall: Rules" menu tab...
                if (isset($node->enable) && $node->if != 'lo0') {
                    $iftargets['fw'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
                }
                // "Services: DHCPv[46]" menu tab:
                if (empty($node->virtual) && isset($node->enable)) {
                    if (!empty(filter_var($node->ipaddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))) {
                        $iftargets['dhcp4'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
                    }
                    if (!empty(filter_var($node->ipaddrv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) || !empty($node->dhcpd6track6allowoverride)) {
                        $iftargets['dhcp6'][$key] = !empty($node->descr) ? (string)$node->descr : strtoupper($key);
                    }
                }
            }
        }

        foreach (array_keys($iftargets) as $tab) {
            natcasesort($iftargets[$tab]);
        }

        // add groups and interfaces to "Interfaces" menu tab...
        $ordid = count($ifgroups_seq) > 0 ? max($ifgroups_seq) : 0;
        foreach ($iftargets['if'] as $key => $descr) {
            if (array_key_exists($key, $iftargets['gr'])) {
                $this->appendItem('Interfaces', $key, array(
                    'visiblename' => '[' . $descr . ']',
                    'cssclass' => 'fa fa-sitemap',
                    'order' => isset($ifgroups_seq[$key]) ? $ifgroups_seq[$key] : $ordid++,
                ));
            } elseif (!array_key_exists($key, $ifgroups)) {
                $this->appendItem('Interfaces', $key, array(
                    'url' => '/interfaces.php?if=' . $key,
                    'visiblename' => '[' . $descr . ']',
                    'cssclass' => 'fa fa-sitemap',
                    'order' => $ordid++,
                ));
            }
        }

        foreach ($ifgroups as $key => $groupings) {
            $first = true;
            foreach ($groupings as $grouping) {
                if (empty($iftargets['if'][$key])) {
                    // referential integrity between ifgroups and interfaces isn't assured, skip when interface doesn't exist
                    continue;
                }
                $this->appendItem('Interfaces.' . $grouping, $key, array(
                    'url' => '/interfaces.php?if=' . $key . '&group=' . $grouping,
                    'visiblename' => '[' . $iftargets['if'][$key] . ']',
                    'order' => array_search($key, array_keys($iftargets['if']))
                ));
                if ($first) {
                    $this->appendItem('Interfaces.' . $grouping . '.' . $key, 'Origin', array(
                        'url' => '/interfaces.php?if=' . $key,
                        'visibility' => 'hidden',
                    ));
                    $first = false;
                }
            }
        }

        $ordid = 100;
        foreach ($iftargets['wl'] as $key => $descr) {
            $this->appendItem('Interfaces.Wireless', $key, array(
                'visiblename' => sprintf(gettext('%s Status'), $descr),
                'url' => '/status_wireless.php?if=' . $key,
                'order' => $ordid++,
            ));
        }

        // add interfaces to "Firewall: Rules" menu tab...
        $iftargets['fw'] = array_merge(array('FloatingRules' => gettext('Floating')), $iftargets['fw']);
        $ordid = 0;
        foreach ($iftargets['fw'] as $key => $descr) {
            $this->appendItem('Firewall.Rules', $key, array(
                'url' => '/firewall_rules.php?if=' . $key,
                'visiblename' => $descr,
                'order' => $ordid++,
            ));
            $this->appendItem('Firewall.Rules.' . $key, 'Select' . $key, array(
                'url' => '/firewall_rules.php?if=' . $key . '&*',
                'visibility' => 'hidden',
            ));
            if ($key == 'FloatingRules') {
                $this->appendItem('Firewall.Rules.' . $key, 'Top' . $key, array(
                    'url' => '/firewall_rules.php',
                    'visibility' => 'hidden',
                ));
            }
            $this->appendItem('Firewall.Rules.' . $key, 'Add' . $key, array(
                'url' => '/firewall_rules_edit.php?if=' . $key,
                'visibility' => 'hidden',
            ));
            $this->appendItem('Firewall.Rules.' . $key, 'Edit' . $key, array(
                'url' => '/firewall_rules_edit.php?if=' . $key . '&*',
                'visibility' => 'hidden',
            ));
        }

        // add interfaces to "Services: DHCPv[46]" menu tab:
        $ordid = 0;
        foreach ($iftargets['dhcp4'] as $key => $descr) {
            $this->appendItem('Services.ISC_DHCPv4', $key, array(
                'url' => '/services_dhcp.php?if=' . $key,
                'visiblename' => "[$descr]",
                'order' => $ordid++,
            ));
            $this->appendItem('Services.ISC_DHCPv4.' . $key, 'Edit' . $key, array(
                'url' => '/services_dhcp.php?if=' . $key . '&*',
                'visibility' => 'hidden',
            ));
            $this->appendItem('Services.ISC_DHCPv4.' . $key, 'AddStatic' . $key, array(
                'url' => '/services_dhcp_edit.php?if=' . $key,
                'visibility' => 'hidden',
            ));
            $this->appendItem('Services.ISC_DHCPv4.' . $key, 'EditStatic' . $key, array(
                'url' => '/services_dhcp_edit.php?if=' . $key . '&*',
                'visibility' => 'hidden',
            ));
        }
        $ordid = 0;
        foreach ($iftargets['dhcp6'] as $key => $descr) {
            $this->appendItem('Services.ISC_DHCPv6', $key, array(
                'url' => '/services_dhcpv6.php?if=' . $key,
                'visiblename' => "[$descr]",
                'order' => $ordid++,
            ));
            $this->appendItem('Services.ISC_DHCPv6.' . $key, 'Add' . $key, array(
                'url' => '/services_dhcpv6_edit.php?if=' . $key,
                'visibility' => 'hidden',
            ));
            $this->appendItem('Services.ISC_DHCPv6.' . $key, 'Edit' . $key, array(
                'url' => '/services_dhcpv6_edit.php?if=' . $key . '&*',
                'visibility' => 'hidden',
            ));
            $this->appendItem('Services.RouterAdv', $key, array(
                'url' => '/services_router_advertisements.php?if=' . $key,
                'visiblename' => "[$descr]",
                'order' => $ordid++,
            ));
        }
    }

    /**
     * return full menu system including selected items
     * @param string $url current location
     * @return array
     */
    public function getItems($url)
    {
        $this->root->toggleSelected($url);
        $menu = $this->root->getChildren();

        return $menu;
    }

    /**
     * return the currently selected page's breadcrumbs
     * @return array
     */
    public function getBreadcrumbs()
    {
        $nodes = $this->root->getChildren();
        $breadcrumbs = array();

        while ($nodes != null) {
            $next = null;
            foreach ($nodes as $node) {
                if ($node->Selected) {
                    /* ignore client-side anchor in breadcrumb */
                    if (!empty($node->Url) && strpos($node->Url, '#') !== false) {
                        $next = null;
                        break;
                    }
                    $breadcrumbs[] = array('name' => $node->VisibleName);
                    /* only go as far as the first reachable URL */
                    $next = empty($node->Url) ? $node->Children : null;
                    break;
                }
            }
            $nodes = $next;
        }

        return $breadcrumbs;
    }
}

Zerion Mini Shell 1.0