setContainer($container); } return $this; } // Accessors: /** * Sets CSS class to use for the first 'ul' element when rendering * * @param string $ulClass CSS class to set * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setUlClass($ulClass) { if (is_string($ulClass)) { $this->_ulClass = $ulClass; } return $this; } /** * Returns CSS class to use for the first 'ul' element when rendering * * @return string CSS class */ public function getUlClass() { return $this->_ulClass; } /** * Sets unique identifier (id) to use for the first 'ul' element when * rendering * * @param string|null $ulId Unique identifier (id) to set * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setUlId($ulId) { if (is_string($ulId)) { $this->_ulId = $ulId; } return $this; } /** * Returns unique identifier (id) to use for the first 'ul' element when * rendering * * @return string|null Unique identifier (id); Default is 'null' */ public function getUlId() { return $this->_ulId; } /** * Sets CSS class to use for the active elements when rendering * * @param string $activeClass CSS class to set * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setActiveClass($activeClass) { if (is_string($activeClass)) { $this->_activeClass = $activeClass; } return $this; } /** * Returns CSS class to use for the active elements when rendering * * @return string CSS class */ public function getActiveClass() { return $this->_activeClass; } /** * Sets CSS class to use for the parent li elements when rendering * * @param string $parentClass CSS class to set to parents * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setParentClass($parentClass) { if (is_string($parentClass)) { $this->_parentClass = $parentClass; } return $this; } /** * Returns CSS class to use for the parent lie elements when rendering * * @return string CSS class */ public function getParentClass() { return $this->_parentClass; } /** * Enables/disables rendering of parent class to the li element * * @param bool $flag [optional] render with parent * class. Default is true. * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setRenderParentClass($flag = true) { $this->_renderParentClass = (bool) $flag; return $this; } /** * Returns flag indicating whether parent class should be rendered to the li * element * * @return bool whether parent class should be rendered */ public function getRenderParentClass() { return $this->_renderParentClass; } /** * Sets a flag indicating whether only active branch should be rendered * * @param bool $flag [optional] render only active * branch. Default is true. * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setOnlyActiveBranch($flag = true) { $this->_onlyActiveBranch = (bool) $flag; return $this; } /** * Returns a flag indicating whether only active branch should be rendered * * By default, this value is false, meaning the entire menu will be * be rendered. * * @return bool whether only active branch should be rendered */ public function getOnlyActiveBranch() { return $this->_onlyActiveBranch; } /** * Sets a flag indicating whether to expand all sibling nodes of the active branch * * @param bool $flag [optional] expand all siblings of * nodes in the active branch. Default is true. * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setExpandSiblingNodesOfActiveBranch($flag = true) { $this->_expandSiblingNodesOfActiveBranch = (bool) $flag; return $this; } /** * Returns a flag indicating whether to expand all sibling nodes of the active branch * * By default, this value is false, meaning the entire menu will be * be rendered. * * @return bool whether siblings of nodes in the active branch should be expanded */ public function getExpandSiblingNodesOfActiveBranch() { return $this->_expandSiblingNodesOfActiveBranch; } /** * Enables/disables rendering of parents when only rendering active branch * * See {@link setOnlyActiveBranch()} for more information. * * @param bool $flag [optional] render parents when * rendering active branch. * Default is true. * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setRenderParents($flag = true) { $this->_renderParents = (bool) $flag; return $this; } /** * Returns flag indicating whether parents should be rendered when rendering * only the active branch * * By default, this value is true. * * @return bool whether parents should be rendered */ public function getRenderParents() { return $this->_renderParents; } /** * Sets which partial view script to use for rendering menu * * @param string|array $partial partial view script or null. If * an array is given, it is * expected to contain two values; * the partial view script to use, * and the module where the script * can be found. * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function setPartial($partial) { if (null === $partial || is_string($partial) || is_array($partial)) { $this->_partial = $partial; } return $this; } /** * Returns partial view script to use for rendering menu * * @return string|array|null */ public function getPartial() { return $this->_partial; } /** * Adds CSS class from page to li element * * Before: * *
  • * Bar *
  • *
    * * After: * *
  • * Bar *
  • *
    * * @param bool $flag [optional] adds CSS class from * page to li element * * @return Zend_View_Helper_Navigation_Menu fluent interface, returns self */ public function addPageClassToLi($flag = true) { $this->_addPageClassToLi = (bool) $flag; return $this; } /** * Returns a flag indicating whether the CSS class from page to be added to * li element * * @return bool */ public function getAddPageClassToLi() { return $this->_addPageClassToLi; } /** * Set the inner indentation string for using in {@link render()}, optionally * a number of spaces to indent with * * @param string|int $indent indentation string or * number of spaces * @return Zend_View_Helper_Navigation_HelperAbstract fluent interface, * returns self */ public function setInnerIndent($indent) { $this->_innerIndent = $this->_getWhitespace($indent); return $this; } /** * Returns inner indentation (format output is respected) * * @see getFormatOutput() * * @return string indentation string or an empty string */ public function getInnerIndent() { if (false === $this->getFormatOutput()) { return ''; } return $this->_innerIndent; } // Public methods: /** * Returns an HTML string containing an 'a' element for the given page if * the page's href is not empty, and a 'span' element if it is empty * * Overrides {@link Zend_View_Helper_Navigation_Abstract::htmlify()}. * * @param Zend_Navigation_Page $page page to generate HTML for * @return string HTML string for the given page */ public function htmlify(Zend_Navigation_Page $page) { // get label and title for translating $label = $page->getLabel(); $title = $page->getTitle(); // translate label and title? if ($this->getUseTranslator() && $t = $this->getTranslator()) { if (is_string($label) && !empty($label)) { $label = $t->translate($label); } if (is_string($title) && !empty($title)) { $title = $t->translate($title); } } // get attribs for element $attribs = array( 'id' => $page->getId(), 'title' => $title, ); if (false === $this->getAddPageClassToLi()) { $attribs['class'] = $page->getClass(); } // does page have a href? if ($href = $page->getHref()) { $element = 'a'; $attribs['href'] = $href; $attribs['target'] = $page->getTarget(); $attribs['accesskey'] = $page->getAccessKey(); } else { $element = 'span'; } // Add custom HTML attributes $attribs = array_merge($attribs, $page->getCustomHtmlAttribs()); return '<' . $element . $this->_htmlAttribs($attribs) . '>' . $this->view->escape($label) . ''; } /** * Normalizes given render options * * @param array $options [optional] options to normalize * @return array normalized options */ protected function _normalizeOptions(array $options = array()) { // Ident if (isset($options['indent'])) { $options['indent'] = $this->_getWhitespace($options['indent']); } else { $options['indent'] = $this->getIndent(); } // Inner ident if (isset($options['innerIndent'])) { $options['innerIndent'] = $this->_getWhitespace($options['innerIndent']); } else { $options['innerIndent'] = $this->getInnerIndent(); } // UL class if (isset($options['ulClass']) && $options['ulClass'] !== null) { $options['ulClass'] = (string) $options['ulClass']; } else { $options['ulClass'] = $this->getUlClass(); } // UL id if (isset($options['ulId']) && $options['ulId'] !== null) { $options['ulId'] = (string) $options['ulId']; } else { $options['ulId'] = $this->getUlId(); } // Active class if (isset($options['activeClass']) && $options['activeClass'] !== null ) { $options['activeClass'] = (string) $options['activeClass']; } else { $options['activeClass'] = $this->getActiveClass(); } // Parent class if (isset($options['parentClass']) && $options['parentClass'] !== null) { $options['parentClass'] = (string) $options['parentClass']; } else { $options['parentClass'] = $this->getParentClass(); } // Minimum depth if (array_key_exists('minDepth', $options)) { if (null !== $options['minDepth']) { $options['minDepth'] = (int) $options['minDepth']; } } else { $options['minDepth'] = $this->getMinDepth(); } if ($options['minDepth'] < 0 || $options['minDepth'] === null) { $options['minDepth'] = 0; } // Maximum depth if (array_key_exists('maxDepth', $options)) { if (null !== $options['maxDepth']) { $options['maxDepth'] = (int) $options['maxDepth']; } } else { $options['maxDepth'] = $this->getMaxDepth(); } // Only active branch if (!isset($options['onlyActiveBranch'])) { $options['onlyActiveBranch'] = $this->getOnlyActiveBranch(); } // Expand sibling nodes of active branch if (!isset($options['expandSiblingNodesOfActiveBranch'])) { $options['expandSiblingNodesOfActiveBranch'] = $this->getExpandSiblingNodesOfActiveBranch(); } // Render parents? if (!isset($options['renderParents'])) { $options['renderParents'] = $this->getRenderParents(); } // Render parent class? if (!isset($options['renderParentClass'])) { $options['renderParentClass'] = $this->getRenderParentClass(); } // Add page CSS class to LI element if (!isset($options['addPageClassToLi'])) { $options['addPageClassToLi'] = $this->getAddPageClassToLi(); } return $options; } // Render methods: /** * Renders the deepest active menu within [$minDepth, $maxDeth], (called * from {@link renderMenu()}) * * @param Zend_Navigation_Container $container container to render * @param string $ulClass CSS class for first UL * @param string $indent initial indentation * @param string $innerIndent inner indentation * @param int|null $minDepth minimum depth * @param int|null $maxDepth maximum depth * @param string|null $ulId unique identifier (id) * for first UL * @param bool $addPageClassToLi adds CSS class from * page to li element * @param string|null $activeClass CSS class for active * element * @param string $parentClass CSS class for parent * li's * @param bool $renderParentClass Render parent class? * @return string rendered menu (HTML) */ protected function _renderDeepestMenu(Zend_Navigation_Container $container, $ulClass, $indent, $innerIndent, $minDepth, $maxDepth, $ulId, $addPageClassToLi, $activeClass, $parentClass, $renderParentClass) { if (!$active = $this->findActive($container, $minDepth - 1, $maxDepth)) { return ''; } // special case if active page is one below minDepth if ($active['depth'] < $minDepth) { if (!$active['page']->hasPages()) { return ''; } } else if (!$active['page']->hasPages()) { // found pages has no children; render siblings $active['page'] = $active['page']->getParent(); } else if (is_int($maxDepth) && $active['depth'] + 1 > $maxDepth) { // children are below max depth; render siblings $active['page'] = $active['page']->getParent(); } $attribs = array( 'class' => $ulClass, 'id' => $ulId, ); // We don't need a prefix for the menu ID (backup) $skipValue = $this->_skipPrefixForId; $this->skipPrefixForId(); $html = $indent . '_htmlAttribs($attribs) . '>' . $this->getEOL(); // Reset prefix for IDs $this->_skipPrefixForId = $skipValue; foreach ($active['page'] as $subPage) { if (!$this->accept($subPage)) { continue; } $liClass = ''; if ($subPage->isActive(true) && $addPageClassToLi) { $liClass = $this->_htmlAttribs( array('class' => $activeClass . ' ' . $subPage->getClass()) ); } else if ($subPage->isActive(true)) { $liClass = $this->_htmlAttribs(array('class' => $activeClass)); } else if ($addPageClassToLi) { $liClass = $this->_htmlAttribs( array('class' => $subPage->getClass()) ); } $html .= $indent . $innerIndent . '' . $this->getEOL(); $html .= $indent . str_repeat($innerIndent, 2) . $this->htmlify($subPage) . $this->getEOL(); $html .= $indent . $innerIndent . '' . $this->getEOL(); } $html .= $indent . ''; return $html; } /** * Renders a normal menu (called from {@link renderMenu()}) * * @param Zend_Navigation_Container $container container to render * @param string $ulClass CSS class for first UL * @param string $indent initial indentation * @param string $innerIndent inner indentation * @param int|null $minDepth minimum depth * @param int|null $maxDepth maximum depth * @param bool $onlyActive render only active branch? * @param bool $expandSibs render siblings of active * branch nodes? * @param string|null $ulId unique identifier (id) * for first UL * @param bool $addPageClassToLi adds CSS class from * page to li element * @param string|null $activeClass CSS class for active * element * @param string $parentClass CSS class for parent * li's * @param bool $renderParentClass Render parent class? * @return string rendered menu (HTML) */ protected function _renderMenu(Zend_Navigation_Container $container, $ulClass, $indent, $innerIndent, $minDepth, $maxDepth, $onlyActive, $expandSibs, $ulId, $addPageClassToLi, $activeClass, $parentClass, $renderParentClass) { $html = ''; // find deepest active if ($found = $this->findActive($container, $minDepth, $maxDepth)) { $foundPage = $found['page']; $foundDepth = $found['depth']; } else { $foundPage = null; } // create iterator $iterator = new RecursiveIteratorIterator($container, RecursiveIteratorIterator::SELF_FIRST); if (is_int($maxDepth)) { $iterator->setMaxDepth($maxDepth); } // iterate container $prevDepth = -1; foreach ($iterator as $page) { $depth = $iterator->getDepth(); $isActive = $page->isActive(true); if ($depth < $minDepth || !$this->accept($page)) { // page is below minDepth or not accepted by acl/visibilty continue; } else if ($expandSibs && $depth > $minDepth) { // page is not active itself, but might be in the active branch $accept = false; if ($foundPage) { if ($foundPage->hasPage($page)) { // accept if page is a direct child of the active page $accept = true; } else if ($page->getParent()->isActive(true)) { // page is a sibling of the active branch... $accept = true; } } if (!$isActive && !$accept) { continue; } } else if ($onlyActive && !$isActive) { // page is not active itself, but might be in the active branch $accept = false; if ($foundPage) { if ($foundPage->hasPage($page)) { // accept if page is a direct child of the active page $accept = true; } else if ($foundPage->getParent()->hasPage($page)) { // page is a sibling of the active page... if (!$foundPage->hasPages() || is_int($maxDepth) && $foundDepth + 1 > $maxDepth) { // accept if active page has no children, or the // children are too deep to be rendered $accept = true; } } } if (!$accept) { continue; } } // make sure indentation is correct $depth -= $minDepth; $myIndent = $indent . str_repeat($innerIndent, $depth * 2); if ($depth > $prevDepth) { $attribs = array(); // start new ul tag if (0 == $depth) { $attribs = array( 'class' => $ulClass, 'id' => $ulId, ); } // We don't need a prefix for the menu ID (backup) $skipValue = $this->_skipPrefixForId; $this->skipPrefixForId(); $html .= $myIndent . '_htmlAttribs($attribs) . '>' . $this->getEOL(); // Reset prefix for IDs $this->_skipPrefixForId = $skipValue; } else if ($prevDepth > $depth) { // close li/ul tags until we're at current depth for ($i = $prevDepth; $i > $depth; $i--) { $ind = $indent . str_repeat($innerIndent, $i * 2); $html .= $ind . $innerIndent . '' . $this->getEOL(); $html .= $ind . '' . $this->getEOL(); } // close previous li tag $html .= $myIndent . $innerIndent . '' . $this->getEOL(); } else { // close previous li tag $html .= $myIndent . $innerIndent . '' . $this->getEOL(); } // render li tag and page $liClasses = array(); // Is page active? if ($isActive) { $liClasses[] = $activeClass; } // Add CSS class from page to LI? if ($addPageClassToLi) { $liClasses[] = $page->getClass(); } // Add CSS class for parents to LI? if ($renderParentClass && $page->hasChildren()) { // Check max depth if ((is_int($maxDepth) && ($depth + 1 < $maxDepth)) || !is_int($maxDepth) ) { $liClasses[] = $parentClass; } } $html .= $myIndent . $innerIndent . '_htmlAttribs(array('class' => implode(' ', $liClasses))) . '>' . $this->getEOL() . $myIndent . str_repeat($innerIndent, 2) . $this->htmlify($page) . $this->getEOL(); // store as previous depth for next iteration $prevDepth = $depth; } if ($html) { // done iterating container; close open ul/li tags for ($i = $prevDepth+1; $i > 0; $i--) { $myIndent = $indent . str_repeat($innerIndent . $innerIndent, $i - 1); $html .= $myIndent . $innerIndent . '' . $this->getEOL() . $myIndent . '' . $this->getEOL(); } $html = rtrim($html, $this->getEOL()); } return $html; } /** * Renders helper * * Renders a HTML 'ul' for the given $container. If $container is not given, * the container registered in the helper will be used. * * Available $options: * * * @param Zend_Navigation_Container $container [optional] container to * create menu from. Default * is to use the container * retrieved from * {@link getContainer()}. * @param array $options [optional] options for * controlling rendering * @return string rendered menu */ public function renderMenu(Zend_Navigation_Container $container = null, array $options = array()) { if (null === $container) { $container = $this->getContainer(); } $options = $this->_normalizeOptions($options); if ($options['onlyActiveBranch'] && !$options['renderParents']) { $html = $this->_renderDeepestMenu( $container, $options['ulClass'], $options['indent'], $options['innerIndent'], $options['minDepth'], $options['maxDepth'], $options['ulId'], $options['addPageClassToLi'], $options['activeClass'], $options['parentClass'], $options['renderParentClass'] ); } else { $html = $this->_renderMenu( $container, $options['ulClass'], $options['indent'], $options['innerIndent'], $options['minDepth'], $options['maxDepth'], $options['onlyActiveBranch'], $options['expandSiblingNodesOfActiveBranch'], $options['ulId'], $options['addPageClassToLi'], $options['activeClass'], $options['parentClass'], $options['renderParentClass'] ); } return $html; } /** * Renders the inner-most sub menu for the active page in the $container * * This is a convenience method which is equivalent to the following call: * * renderMenu($container, array( * 'indent' => $indent, * 'ulClass' => $ulClass, * 'minDepth' => null, * 'maxDepth' => null, * 'onlyActiveBranch' => true, * 'renderParents' => false * )); * * * @param Zend_Navigation_Container $container [optional] container to * render. Default is to render * the container registered in * the helper. * @param string|null $ulClass [optional] CSS class to * use for UL element. Default * is to use the value from * {@link getUlClass()}. * @param string|int $indent [optional] indentation as * a string or number of * spaces. Default is to use * the value retrieved from * {@link getIndent()}. * @param string|null $ulId [optional] Unique identifier * (id) use for UL element * @param bool $addPageClassToLi adds CSS class from * page to li element * @param string|int $innerIndent [optional] inner * indentation as a string * or number of spaces. * Default is to use the * {@link getInnerIndent()}. * @return string rendered content */ public function renderSubMenu(Zend_Navigation_Container $container = null, $ulClass = null, $indent = null, $ulId = null, $addPageClassToLi = false, $innerIndent = null) { return $this->renderMenu($container, array( 'indent' => $indent, 'innerIndent' => $innerIndent, 'ulClass' => $ulClass, 'minDepth' => null, 'maxDepth' => null, 'onlyActiveBranch' => true, 'renderParents' => false, 'ulId' => $ulId, 'addPageClassToLi' => $addPageClassToLi, )); } /** * Renders the given $container by invoking the partial view helper * * The container will simply be passed on as a model to the view script * as-is, and will be available in the partial script as 'container', e.g. * echo 'Number of pages: ', count($this->container);. * * @param Zend_Navigation_Container $container [optional] container to * pass to view script. Default * is to use the container * registered in the helper. * @param string|array $partial [optional] partial view * script to use. Default is to * use the partial registered * in the helper. If an array * is given, it is expected to * contain two values; the * partial view script to use, * and the module where the * script can be found. * @return string helper output * * @throws Zend_View_Exception When no partial script is set */ public function renderPartial(Zend_Navigation_Container $container = null, $partial = null) { if (null === $container) { $container = $this->getContainer(); } if (null === $partial) { $partial = $this->getPartial(); } if (empty($partial)) { require_once 'Zend/View/Exception.php'; $e = new Zend_View_Exception( 'Unable to render menu: No partial view script provided' ); $e->setView($this->view); throw $e; } $model = array( 'container' => $container ); if (is_array($partial)) { if (count($partial) != 2) { require_once 'Zend/View/Exception.php'; $e = new Zend_View_Exception( 'Unable to render menu: A view partial supplied as ' . 'an array must contain two values: partial view ' . 'script and module where script can be found' ); $e->setView($this->view); throw $e; } return $this->view->partial($partial[0], $partial[1], $model); } return $this->view->partial($partial, null, $model); } // Zend_View_Helper_Navigation_Helper: /** * Renders menu * * Implements {@link Zend_View_Helper_Navigation_Helper::render()}. * * If a partial view is registered in the helper, the menu will be rendered * using the given partial script. If no partial is registered, the menu * will be rendered as an 'ul' element by the helper's internal method. * * @see renderPartial() * @see renderMenu() * * @param Zend_Navigation_Container $container [optional] container to * render. Default is to * render the container * registered in the helper. * @return string helper output */ public function render(Zend_Navigation_Container $container = null) { if ($partial = $this->getPartial()) { return $this->renderPartial($container, $partial); } else { return $this->renderMenu($container); } } }