<nav>
<ol class="boxMenu">
{foreach from=$menuItemNodeList item=menuItemNode}
- <li{if $menuItemNode->hasChildren()} class="boxMenuHasChildren"{/if}>
+ <li class="{if $menuItemNode->isActiveNode()}active{/if}{if $menuItemNode->hasChildren()} boxMenuHasChildren{/if}">
<a href="{$menuItemNode->getMenuItem()->getURL()}" class="boxMenuLink">
<span class="boxMenuLinkTitle">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</span>
{if $menuItemNode->getMenuItem()->getOutstandingItems() > 0}
namespace wcf\data\menu\item;
use wcf\data\page\Page;
use wcf\data\DatabaseObject;
+use wcf\data\page\PageCache;
use wcf\system\exception\SystemException;
use wcf\system\page\handler\ILookupPageHandler;
use wcf\system\page\handler\IMenuPageHandler;
*/
public function getPage() {
if ($this->page === null && $this->pageID) {
- $this->page = new Page($this->pageID);
+ $this->page = PageCache::getInstance()->getPage($this->pageID);
}
return $this->page;
*/
protected $depth = 0;
+ /**
+ * true if item or one of its children is active
+ * @var boolean
+ */
+ protected $isActive = false;
+
/**
* menu item object
* @var MenuItem
return $i;
}
+ /**
+ * Marks this item and all its direct ancestors as active.
+ */
+ public function setIsActive() {
+ $this->isActive = true;
+
+ // propagate active state to immediate parent
+ if ($this->parentNode) {
+ $this->parentNode->setIsActive();
+ }
+ }
+
+ /**
+ * Returns true if this item (or one of its children) is marked as active.
+ *
+ * @return boolean
+ */
+ public function isActiveNode() {
+ return $this->isActive;
+ }
+
/**
* @inheritDoc
*/
<?php
namespace wcf\data\menu\item;
+use wcf\system\page\PageLocationManager;
/**
* Represents a menu item node tree.
$menuItemList->readObjects();
}
+ // find possible active menu items
+ $activeMenuItems = [];
+ $possibleLocations = PageLocationManager::getInstance()->getLocations();
+ $length = count($possibleLocations);
+ foreach ($menuItemList as $menuItem) {
+ for ($i = 0; $i < $length; $i++) {
+ if ($menuItem->pageID == $possibleLocations[$i]['pageID'] && $menuItem->pageObjectID == $possibleLocations[$i]['pageObjectID']) {
+ if (!isset($activeMenuItems[$i])) {
+ $activeMenuItems[$i] = [];
+ }
+
+ $activeMenuItems[$i][] = $menuItem->itemID;
+ }
+ }
+ }
+
// build menu structure
foreach ($menuItemList as $menuItem) {
$this->menuItems[$menuItem->itemID] = $menuItem;
// generate node tree
$this->node = new MenuItemNode();
$this->node->setChildren($this->generateNodeTree(null, $this->node));
+
+ // mark nodes as active
+ if (!empty($activeMenuItems)) {
+ $nodeList = $this->getNodeList();
+ foreach ($activeMenuItems as $itemIDs) {
+ for ($i = 0, $length = count($itemIDs); $i < $length; $i++) {
+ /** @var MenuItemNode $node */
+ foreach ($nodeList as $node) {
+ if ($node->getMenuItem()->itemID == $itemIDs[$i]) {
+ $node->setIsActive();
+
+ // only one effective item can be marked as active, use the first
+ // occurence with the highest priority and ignore everything else
+ return;
+ }
+ }
+ }
+ }
+ }
}
/**
--- /dev/null
+<?php
+namespace wcf\data\page;
+use wcf\system\cache\builder\PageCacheBuilder;
+use wcf\system\SingletonFactory;
+
+/**
+ * Provides access to the page cache.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.page
+ * @category Community Framework
+ */
+class PageCache extends SingletonFactory {
+ /**
+ * page cache
+ * @var array
+ */
+ protected $cache;
+
+ /**
+ * @inheritDoc
+ */
+ protected function init() {
+ $this->cache = PageCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * Returns a page by page id or null.
+ *
+ * @param integer $pageID page id
+ * @return Page|null
+ */
+ public function getPage($pageID) {
+ if (isset($this->cache['pages'][$pageID])) {
+ return $this->cache['pages'][$pageID];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a page by controller or null.
+ *
+ * @param string $controller controller class name
+ * @return Page|null
+ */
+ public function getPageByController($controller) {
+ if (isset($this->cache['controller'][$controller])) {
+ return $this->getPage($this->cache['controller'][$controller]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a page by its internal identifier or null.
+ *
+ * @param string $identifier internal identifier
+ * @return Page|null
+ */
+ public function getPageByIdentifier($identifier) {
+ if (isset($this->cache['identifier'][$identifier])) {
+ return $this->getPage($this->cache['identifier'][$identifier]);
+ }
+
+ return null;
+ }
+}
use wcf\system\exception\SystemException;
/**
- * Basis class for singleton classes.
+ * Base class for singleton factories.
*
* @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
+ * @copyright 2001-2016 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @package com.woltlab.wcf
* @subpackage system
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\page\PageList;
+
+/**
+ * Caches the page data.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class PageCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @inheritDoc
+ */
+ public function rebuild(array $parameters) {
+ $data = [
+ 'identifier' => [],
+ 'controller' => [],
+ 'pages' => []
+ ];
+
+ $pageList = new PageList();
+ $pageList->readObjects();
+ $data['pages'] = $pageList->getObjects();
+
+ // build lookup table
+ foreach ($pageList as $page) {
+ $data['identifier'][$page->identifier] = $page->pageID;
+ $data['controller'][$page->controller] = $page->pageID;
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\page;
+use wcf\data\page\PageCache;
+use wcf\system\exception\SystemException;
+use wcf\system\request\RequestHandler;
+use wcf\system\SingletonFactory;
+
+/**
+ * Handles page locations for use with menu active markings.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2016 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.page
+ * @category Community Framework
+ */
+class PageLocationManager extends SingletonFactory {
+ /**
+ * list of locations with descending priority
+ * @var array
+ */
+ protected $stack = [];
+
+ /**
+ * @inheritDoc
+ */
+ public function init() {
+ $pageID = $pageObjectID = 0;
+
+ $activeRequest = RequestHandler::getInstance()->getActiveRequest();
+ $metaData = $activeRequest->getMetaData();
+ if (isset($metaData['cms'])) {
+ $pageID = $metaData['cms']['pageID'];
+ }
+ else {
+ $page = PageCache::getInstance()->getPageByController($activeRequest->getClassName());
+ if ($page !== null) {
+ $pageID = $page->pageID;
+
+ if (!empty($_REQUEST['id'])) $pageObjectID = intval($_REQUEST['id']);
+ }
+ }
+
+ if ($pageID !== null) {
+ $this->stack[] = [
+ 'pageID' => $pageID,
+ 'pageObjectID' => $pageObjectID
+ ];
+ }
+ }
+
+ /**
+ * Appends a parent location to the stack, the later it is added the lower
+ * is its assumed priority when matching suitable menu items.
+ *
+ * @param string $identifier internal page identifier
+ * @param integer $pageObjectID page object id
+ * @throws SystemException
+ */
+ public function addParentLocation($identifier, $pageObjectID = 0) {
+ $page = PageCache::getInstance()->getPageByIdentifier($identifier);
+ if ($page === null) {
+ throw new SystemException("Unknown page identifier '".$identifier."'.");
+ }
+
+ $this->stack[] = [
+ 'pageID' => $page->pageID,
+ 'pageObjectID' => $pageObjectID
+ ];
+ }
+
+ /**
+ * Returns the list of locations with descending priority.
+ *
+ * @return array
+ */
+ public function getLocations() {
+ return $this->stack;
+ }
+}