%PDF- %PDF-
| Direktori : /proc/self/root/backups/router/usr/local/opnsense/mvc/app/models/OPNsense/Base/Menu/ |
| Current File : //proc/self/root/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;
}
}