Added proper marking as active for menus
authorAlexander Ebert <ebert@woltlab.com>
Wed, 23 Mar 2016 17:29:41 +0000 (18:29 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 23 Mar 2016 17:29:41 +0000 (18:29 +0100)
com.woltlab.wcf/templates/__menu.tpl
wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemNode.class.php
wcfsetup/install/files/lib/data/menu/item/MenuItemNodeTree.class.php
wcfsetup/install/files/lib/data/page/PageCache.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/SingletonFactory.class.php
wcfsetup/install/files/lib/system/cache/builder/PageCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/page/PageLocationManager.class.php [new file with mode: 0644]

index a794c46213b1a2743f2f2036375e3396aca6d8e0..ceaa11660bde467a10c1fbe6c9ba5731687d6538 100644 (file)
@@ -1,7 +1,7 @@
 <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}
index da5576cb495cef6c10a3d17d1b7ee60cab653f17..bb5d50a0c1100b260b05408c7e2aa7d90c669d18 100644 (file)
@@ -2,6 +2,7 @@
 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;
@@ -94,7 +95,7 @@ class MenuItem extends DatabaseObject {
         */
        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;
index db8fd5f7fbd77524b40054a16d097ccae9b23b87..cc71b935942ebb3c4b8e31e45f075ec0c0945a26 100644 (file)
@@ -25,6 +25,12 @@ class MenuItemNode implements \Countable, \RecursiveIterator {
         */
        protected $depth = 0;
        
+       /**
+        * true if item or one of its children is active
+        * @var boolean
+        */
+       protected $isActive = false;
+       
        /**
         * menu item object
         * @var MenuItem
@@ -123,6 +129,27 @@ class MenuItemNode implements \Countable, \RecursiveIterator {
                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
         */
index c0c69811becd715abc2c5b96e74e791936a0aedc..933b7d2eeb865b13cb7637ac54abda3f8ad5eabf 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\menu\item;
+use wcf\system\page\PageLocationManager;
 
 /**
  * Represents a menu item node tree.
@@ -60,6 +61,22 @@ class MenuItemNodeTree {
                        $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;
@@ -73,6 +90,25 @@ class MenuItemNodeTree {
                // 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;
+                                               }
+                                       }
+                               }
+                       }
+               }
        }
        
        /**
diff --git a/wcfsetup/install/files/lib/data/page/PageCache.class.php b/wcfsetup/install/files/lib/data/page/PageCache.class.php
new file mode 100644 (file)
index 0000000..71a61e3
--- /dev/null
@@ -0,0 +1,71 @@
+<?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;
+       }
+}
index 677178321cefb64a8857514e865b29243bc594bd..c917d43bc261dbce948ae6144c238951862594b7 100644 (file)
@@ -3,10 +3,10 @@ namespace wcf\system;
 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
diff --git a/wcfsetup/install/files/lib/system/cache/builder/PageCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/PageCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..b0b115f
--- /dev/null
@@ -0,0 +1,38 @@
+<?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;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php b/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php
new file mode 100644 (file)
index 0000000..716612a
--- /dev/null
@@ -0,0 +1,81 @@
+<?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;
+       }
+}