<box>
<position>mainMenu</position>
- <cssclassname>mainMenu</cssclassname>
- <showheader>0</showheader>
- <visibleeveryhwere>1</visibleeveryhwere>
+ <cssClassName>mainMenu</cssClassName>
+ <showHeader>0</showHeader>
+ <visibleEveryhwere>1</visibleEveryhwere>
</box>
</menu>
<box>
<position>footer</position>
- <showheader>0</showheader>
- <visibleeveryhwere>1</visibleeveryhwere>
+ <showHeader>0</showHeader>
+ <visibleEveryhwere>1</visibleEveryhwere>
</box>
</menu>
</import>
<li><a href="https://support.apple.com/kb/ph17191?locale=en_US" class="externalURL" rel="nofollow">Safari</a></li>
<li><a href="http://windows.microsoft.com/en-US/internet-explorer/delete-manage-cookies" class="externalURL" rel="nofollow">Windows Internet Explorer</a></li>
</ul>]]></content>
- <customurl>cookie-policy</customurl>
+ <customURL>cookie-policy</customURL>
</content>
<content language="de">
<li><a href="https://support.apple.com/kb/ph17191?locale=de_DE" class="externalURL" rel="nofollow">Safari</a></li>
<li><a href="http://windows.microsoft.com/de-DE/internet-explorer/delete-manage-cookies" class="externalURL" rel="nofollow">Windows Internet Explorer</a></li>
</ul>]]></content>
- <customurl>cookie-richtlinie</customurl>
+ <customURL>cookie-richtlinie</customURL>
</content>
</page>
<p>If you have questions regarding this privacy policy, please contact us.</p>
<p><small><em>Source: <a rel="nofollow" href="http://forum-template.wikidot.com/legal:privacy-policy" target="_blank">forum-template.wikidot.com</a></em></small></p>]]></content>
- <customurl>privacy-policy</customurl>
+ <customURL>privacy-policy</customURL>
</content>
<content language="de">
<a href="http://twitter.com/account/settings" target="_blank">http://twitter.com/account/settings</a> ändern.</p>
<p><small><em>Quellenangaben: <a rel="nofollow" href="http://www.e-recht24.de/muster-datenschutzerklaerung.html" target="_blank">eRecht24</a>, <a rel="nofollow" href="http://www.e-recht24.de/artikel/datenschutz/6590-facebook-like-button-datenschutz-disclaimer.html" target="_blank">Facebook-Disclaimer von eRecht24</a>, <a rel="nofollow" href="http://www.google.com/intl/de/+/policy/+1button.html" target="_blank">Google +1 Bedingungen</a>, <a rel="nofollow" href="http://twitter.com/privacy" target="_blank">Datenschutzerklärung Twitter</a></em></small></p>]]></content>
- <customurl>datenschutzerklaerung</customurl>
+ <customURL>datenschutzerklaerung</customURL>
</content>
</page>
</import>
<ol class="boxMenu">
{foreach from=$menuItemNodeList item=menuItemNode}
<li{if $menuItemNode->hasChildren()} class="boxMenuHasChildren"{/if}>
- <a href="{$menuItemNode->getMenuItem()->getURL()}">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</a>
+ <a href="{$menuItemNode->getMenuItem()->getURL()}" class="boxMenuLink">
+ <span class="boxMenuLinkTitle">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</span>
+ {if $menuItemNode->getMenuItem()->getOutstandingItems() > 0}
+ <span class="boxMenuLinkOutstandingItems badge badgeInverse">{#$menuItemNode->getMenuItem()->getOutstandingItems()}</span>
+ {/if}
+ </a>
{if $menuItemNode->hasChildren()}<ol class="boxMenuDepth{@$menuItemNode->getDepth()}">{else}</li>{/if}
/**
* CAUTION: This methods does not return the current iterator index,
- * rather than the object key which maps to that index.
+ * but the object key which maps to that index.
*
* @see \Iterator::key()
*/
namespace wcf\data\menu;
use wcf\data\box\Box;
use wcf\data\DatabaseObject;
+use wcf\data\menu\item\MenuItemNodeTree;
use wcf\system\WCF;
/**
protected static $databaseTableIndexName = 'menuID';
/**
- * menu item node list
- * @var \RecursiveIteratorIterator
+ * menu item node tree
+ * @var MenuItemNodeTree
*/
- protected $menuItemNodeList = null;
+ protected $menuItemNodeTree = null;
/**
* box object
* @return \RecursiveIteratorIterator
*/
public function getMenuItemNodeList() {
- if ($this->menuItemNodeList === null) {
- $this->menuItemNodeList = MenuCache::getInstance()->getMenuItemsByMenuID($this->menuID)->getNodeList();
- }
-
- return $this->menuItemNodeList;
+ return $this->getMenuItemNodeTree()->getNodeList();
}
/**
return $this->box;
}
+
+ /**
+ * @return MenuItemNodeTree
+ */
+ protected function getMenuItemNodeTree() {
+ if ($this->menuItemNodeTree === null) {
+ $this->menuItemNodeTree = new MenuItemNodeTree($this->menuID, MenuCache::getInstance()->getMenuItemsByMenuID($this->menuID));
+ }
+
+ return $this->menuItemNodeTree;
+ }
}
<?php
namespace wcf\data\menu;
-use wcf\data\menu\item\MenuItemNodeTree;
+use wcf\data\menu\item\MenuItemList;
use wcf\system\cache\builder\MenuCacheBuilder;
use wcf\system\SingletonFactory;
* Manages the menu cache.
*
* @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 data.menu
* @category Community Framework
+ * @since 2.2
*/
class MenuCache extends SingletonFactory {
/**
protected $cachedMenus;
/**
- * @var MenuItemNodeTree[]
+ * @var MenuItemList[]
*/
protected $cachedMenuItems;
$this->cachedMenuItems = MenuCacheBuilder::getInstance()->getData([], 'menuItems');
}
+ /**
+ * Returns a menu by id.
+ *
+ * @param integer $menuID menu id
+ * @return Menu|null menu object or null if menu id is unknown
+ */
public function getMenuByID($menuID) {
if (isset($this->cachedMenus[$menuID])) {
return $this->cachedMenus[$menuID];
return null;
}
+ /**
+ * Returns a menu item list by menu id.
+ *
+ * @param integer $menuID menu id
+ * @return MenuItemList|null menu item list object or null if menu id is unknown
+ */
public function getMenuItemsByMenuID($menuID) {
if (isset($this->cachedMenuItems[$menuID])) {
return $this->cachedMenuItems[$menuID];
namespace wcf\data\menu\item;
use wcf\data\page\Page;
use wcf\data\DatabaseObject;
+use wcf\system\exception\SystemException;
+use wcf\system\page\handler\ILookupPageHandler;
+use wcf\system\page\handler\IMenuPageHandler;
use wcf\system\WCF;
/**
* Represents a menu item.
*
* @author Marcel Werk
- * @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 data.menu.item
*/
protected static $databaseTableIndexName = 'itemID';
+ /**
+ * @var IMenuPageHandler
+ */
+ protected $handler;
+
/**
* page object
* @var Page
*/
- protected $page = null;
+ protected $page;
/**
* Returns true if the active user can delete this menu item.
* @return string
*/
public function getURL() {
+ if ($this->pageObjectID) {
+ $handler = $this->getMenuPageHandler();
+ if ($handler && $handler instanceof ILookupPageHandler) {
+ return $handler->getLink($this->pageObjectID);
+ }
+ }
+
if ($this->pageID) {
return $this->getPage()->getURL();
}
return $this->page;
}
+
+ /**
+ * Returns false if this item should be hidden from menu.
+ *
+ * @return boolean
+ */
+ public function isVisible() {
+ if ($this->getMenuPageHandler() !== null) {
+ return $this->getMenuPageHandler()->isVisible($this->pageObjectID ?: null);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the number of outstanding items for this menu.
+ *
+ * @return integer
+ */
+ public function getOutstandingItems() {
+ if ($this->getMenuPageHandler() !== null) {
+ return $this->getMenuPageHandler()->getOutstandingItemCount($this->pageObjectID ?: null);
+ }
+
+ return 0;
+ }
+
+ /**
+ * @return IMenuPageHandler
+ */
+ protected function getMenuPageHandler() {
+ $page = $this->getPage();
+ if ($page !== null && $page->handler) {
+ if ($this->handler === null) {
+ $className = $this->getPage()->handler;
+ $this->handler = new $className;
+ if (!($this->handler instanceof IMenuPageHandler)) {
+ throw new SystemException("Expected a valid handler implementing '" . IMenuPageHandler::class . "'.");
+ }
+ }
+ }
+
+ return $this->handler;
+ }
}
* Represents a list of menu items.
*
* @author Marcel Werk
- * @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 data.menu.item
* @inheritDoc
*/
public $className = MenuItem::class;
+
+ /**
+ * Sets the menu items used to improve menu cache performance.
+ *
+ * @param MenuItem[] $menuItems list of menu item objects
+ */
+ public function setMenuItems(array $menuItems) {
+ $this->objects = $menuItems;
+ $this->indexToObject = $this->objectIDs = array_keys($this->objects);
+ }
}
* Represents a menu item node tree.
*
* @author Marcel Werk
- * @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 data.menu.item
/**
* Creates a new MenuItemNodeTree object.
- *
- * @param integer $menuID
+ *
+ * @param integer $menuID menu id
+ * @param MenuItemList $menuItemList optional object to be provided when building the tree from cache
*/
- public function __construct($menuID) {
+ public function __construct($menuID, MenuItemList $menuItemList = null) {
$this->menuID = $menuID;
// load menu items
- $menuItemList = new MenuItemList();
- $menuItemList->getConditionBuilder()->add('menu_item.menuID = ?', array($this->menuID));
- $menuItemList->sqlOrderBy = "menu_item.showOrder";
- $menuItemList->readObjects();
+ if ($menuItemList === null) {
+ $menuItemList = new MenuItemList();
+ $menuItemList->getConditionBuilder()->add('menu_item.menuID = ?', [$this->menuID]);
+ $menuItemList->sqlOrderBy = "menu_item.showOrder";
+ $menuItemList->readObjects();
+ }
foreach ($menuItemList as $menuItem) {
$this->menuItems[$menuItem->itemID] = $menuItem;
if (!isset($this->menuItemStructure[$menuItem->parentItemID])) {
- $this->menuItemStructure[$menuItem->parentItemID] = array();
+ $this->menuItemStructure[$menuItem->parentItemID] = [];
}
$this->menuItemStructure[$menuItem->parentItemID][] = $menuItem->itemID;
}
+ // filter items by visibility
+ foreach ($this->menuItems as $menuItemID => $menuItem) {
+ if (!$menuItem->isVisible()) {
+ unset($this->menuItems[$menuItemID]);
+ unset($this->menuItemStructure[$menuItemID]);
+
+ // remove item from parent item structure
+ $key = array_search($menuItemID, $this->menuItemStructure[$menuItem->parentItemID]);
+ array_splice($this->menuItemStructure[$menuItem->parentItemID], $key, 1);
+ }
+ }
+
// generate node tree
$this->node = new MenuItemNode();
$this->node->setChildren($this->generateNodeTree(null, $this->node));
protected function generateNodeTree($parentID = null, MenuItemNode $parentNode = null) {
$nodes = array();
- $itemIDs = (isset($this->menuItemStructure[$parentID]) ? $this->menuItemStructure[$parentID] : array());
+ $itemIDs = (isset($this->menuItemStructure[$parentID]) ? $this->menuItemStructure[$parentID] : []);
foreach ($itemIDs as $itemID) {
$menuItem = $this->menuItems[$itemID];
$node = new MenuItemNode($parentNode, $menuItem, ($parentNode !== null ? ($parentNode->getDepth() + 1) : 0));
/**
* Returns the page URL.
*
- * @return string
+ * @return string
*/
public function getURL() {
if ($this->controller) {
$controllerParts = explode('\\', $this->controller);
$controllerName = $controllerParts[count($controllerParts) - 1];
$controllerName = preg_replace('/(page|action|form)$/i', '', $controllerName);
+
return LinkHandler::getInstance()->getLink($controllerName, [
'application' => $controllerParts[0]
]);
<?php
namespace wcf\system\cache\builder;
+use wcf\data\menu\item\MenuItemList;
use wcf\data\menu\item\MenuItemNodeTree;
use wcf\data\menu\MenuList;
* Caches menus and menu item node trees.
*
* @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.cache.builder
* @category Community Framework
+ * @since 2.2
*/
class MenuCacheBuilder extends AbstractCacheBuilder {
/**
$menuList = new MenuList();
$menuList->readObjects();
+
+ $menuItemList = new MenuItemList();
+ $menuItemList->sqlOrderBy = "menu_item.showOrder";
+ $menuItemList->readObjects();
+ $menuItems = [];
+ foreach ($menuItemList as $menuItem) {
+ if (!isset($menuItems[$menuItem->menuID])) {
+ $menuItems[$menuItem->menuID] = [];
+ }
+
+ $menuItems[$menuItem->menuID][$menuItem->itemID] = $menuItem;
+ }
+
foreach ($menuList as $menu) {
+ $menuItemList = new MenuItemList();
+ if (!empty($menuItems[$menu->menuID])) {
+ $menuItemList->setMenuItems($menuItems[$menu->menuID]);
+ }
+
$data['menus'][$menu->menuID] = $menu;
- $data['menuItems'][$menu->menuID] = new MenuItemNodeTree($menu->menuID);
+ $data['menuItems'][$menu->menuID] = $menuItemList;
}
return $data;
switch ($boxType) {
case 'system':
- if (empty($data['elements']['classname'])) {
+ if (empty($data['elements']['className'])) {
throw new SystemException("Missing required element 'classname' for 'system'-type box '{$identifier}'");
}
- $className = $data['elements']['classname'];
+ $className = $data['elements']['className'];
break;
case 'html':
'boxType' => $boxType,
'position' => $position,
'showOrder' => $this->getItemOrder($position),
- 'visibleEverywhere' => (isset($data['elements']['visibleeverywhere']) && $data['elements']['visibleeverywhere'] === '1') ? '1' : '0',
+ 'visibleEverywhere' => (!empty($data['elements']['visibleEverywhere'])) ? 1 : 0,
'isMultilingual' => ($isMultilingual) ? '1' : '0',
'cssClassName' => (!empty($data['elements']['cssClassName'])) ? $data['elements']['cssClassName'] : '',
- 'showHeader' => (isset($data['elements']['showheader']) && $data['elements']['showheader'] === '1') ? '1' : '0',
+ 'showHeader' => (!empty($data['elements']['showHeader'])) ? 1 : 0,
'originIsSystem' => 1,
'className' => $className
];
$pageID = $row['pageID'];
}
- $externalURL = (!empty($data['elements']['externalurl'])) ? $data['elements']['externalurl'] : '';
+ $externalURL = (!empty($data['elements']['externalURL'])) ? $data['elements']['externalURL'] : '';
if ($pageID === null && empty($externalURL)) {
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' must either have an associated page or an external url set.");
'name' => $this->getI18nValues($data['elements']['title'], true),
'boxType' => 'menu',
'position' => $position,
- 'showHeader' => (!empty($data['elements']['box']['showheader']) ? 1 : 0),
- 'visibleEverywhere' => (!empty($data['elements']['box']['visibleeveryhwere']) ? 1 : 0),
- 'cssClassName' => (!empty($data['elements']['box']['cssclassname'])) ? $data['elements']['box']['cssclassname'] : '',
+ 'showHeader' => (!empty($data['elements']['box']['showHeader']) ? 1 : 0),
+ 'visibleEverywhere' => (!empty($data['elements']['box']['visibleEverywhere']) ? 1 : 0),
+ 'cssClassName' => (!empty($data['elements']['box']['cssClassName'])) ? $data['elements']['box']['cssClassName'] : '',
'originIsSystem' => 1,
'packageID' => $this->installation->getPackageID()
];
$content = [];
foreach ($data['elements']['content'] as $language => $contentData) {
- if (!RouteHandler::isValidCustomUrl($contentData['customurl'])) {
+ if (!RouteHandler::isValidCustomUrl($contentData['customURL'])) {
throw new SystemException("Invalid custom url for page content '" . $language . "', page identifier '" . $data['attributes']['identifier'] . "'");
}
$content[$language] = [
'content' => $contentData['content'],
- 'customURL' => $contentData['customurl'],
- 'metaDescription' => (!empty($contentData['metadescription'])) ? StringUtil::trim($contentData['metadescription']) : '',
- 'metaKeywords' => (!empty($contentData['metakeywords'])) ? StringUtil::trim($contentData['metakeywords']) : '',
+ 'customURL' => $contentData['customURL'],
+ 'metaDescription' => (!empty($contentData['metaDescription'])) ? StringUtil::trim($contentData['metaDescription']) : '',
+ 'metaKeywords' => (!empty($contentData['metaKeywords'])) ? StringUtil::trim($contentData['metaKeywords']) : '',
'title' => $contentData['title']
];
}
$parentPageID = $row['pageID'];
}
- $customUrl = ($isStatic || empty($data['elements']['customurl'])) ? '' : $data['elements']['customurl'];
- if ($customUrl && !RouteHandler::isValidCustomUrl($customUrl)) {
+ $controllerCustomURL = ($isStatic || empty($data['elements']['controllerCustomURL'])) ? '' : $data['elements']['controllerCustomURL'];
+ if ($controllerCustomURL && !RouteHandler::isValidCustomUrl($controllerCustomURL)) {
throw new SystemException("Invalid custom url for page identifier '" . $data['attributes']['identifier'] . "'");
}
'content' => ($isStatic) ? $data['elements']['content'] : [],
'controller' => ($isStatic) ? '' : $data['elements']['controller'],
'handler' => (!$isStatic && !empty($data['elements']['handler'])) ? $data['elements']['handler'] : '',
- 'controllerCustomURL' => $customUrl,
+ 'controllerCustomURL' => $controllerCustomURL,
'identifier' => $data['attributes']['identifier'],
'isMultilingual' => ($isStatic) ? 1 : 0,
'lastUpdateTime' => TIME_NOW,
'name' => $name,
'originIsSystem' => 1,
- 'parentPageID' => $parentPageID
+ 'parentPageID' => $parentPageID,
+ 'requireObjectID' => (!empty($data['elements']['requireObjectID'])) ? 1 : 0
];
}
* @category Community Framework
* @since 2.2
*/
-abstract class AbstractLookupPageHandler implements ILookupPageHandler {
- /**
- * @inheritDoc
- */
- public function lookup($searchString) {
- return [];
- }
-}
+abstract class AbstractLookupPageHandler extends AbstractMenuPageHandler implements ILookupPageHandler {}
* @since 2.2
*/
interface ILookupPageHandler extends IMenuPageHandler {
+ /**
+ * Returns the link for a page with an object id.
+ *
+ * @param integer $objectID page object id
+ * @return string page url
+ */
+ public function getLink($objectID);
+
/**
* Performs a search for pages using a query string, returning an array containing
* an `objectID => title` relation.
identifier VARCHAR(255) NOT NULL,
title VARCHAR(255) NOT NULL,
pageID INT(10),
+ pageObjectID INT(10) NOT NULL DEFAULT 0,
externalURL VARCHAR(255) NOT NULL DEFAULT '',
showOrder INT(10) NOT NULL DEFAULT 0,
isDisabled TINYINT(1) NOT NULL DEFAULT 0,
controller VARCHAR(255) NOT NULL DEFAULT '',
handler VARCHAR(255) NOT NULL DEFAULT '',
controllerCustomURL VARCHAR(255) NOT NULL DEFAULT '',
+ requireObjectID TINYINT(1) NOT NULL DEFAULT 0,
lastUpdateTime INT(10) NOT NULL DEFAULT 0
);