<?php
/**
* file : /library/App/View/Helper/Navigation/Menu
*/
class App_View_Helper_Navigation_Menu extends Zend_View_Helper_Navigation_Menu
{
protected $_renderParentSiblings = false;
/**
* Returns flag indicating whether parent's siblings should be rendered when rendering
* only the active branch
*
* By default, this value is false.
*
* @return bool whether parent's siblings should be rendered
*/
public function getRenderParentSiblings()
{
return $this->_renderParentSiblings;
}
/**
* Enables/disables rendering of parent's siblings when only rendering active branch
*
* See {@link setOnlyActiveBranch()} for more information.
*
* @param bool $flag [optional] render parent's siblings when
* rendering active branch.
* Default is false.
* @return Zend_View_Helper_Navigation_Menu fluent interface, returns self
*/
public function setRenderParents($flag = false)
{
$this->_renderParentSiblings = (bool) $flag;
return $this;
}
/**
* Normalizes given render options
*
* @param array $options [optional] options to normalize
* @return array normalized options
*/
protected function _normalizeOptions(array $options = array())
{
$options = parent::_normalizeOptions($options);
if (!isset($options['renderParentSiblings'])) {
$options['renderParentSiblings'] = $this->getRenderParentSiblings();
}
return $options;
}
/**
* 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['renderParentSiblings']) {
$html = $this->_renderMenuWithParentSiblings($container,
$options['ulClass'],
$options['indent'],
$options['minDepth'],
$options['maxDepth'],
$options['onlyActiveBranch'],
$options['renderParentSiblings']);
} else if ($options['onlyActiveBranch'] && !$options['renderParents']) {
$html = $this->_renderDeepestMenu($container,
$options['ulClass'],
$options['indent'],
$options['minDepth'],
$options['maxDepth']);
} else {
$html = $this->_renderMenu($container,
$options['ulClass'],
$options['indent'],
$options['minDepth'],
$options['maxDepth'],
$options['onlyActiveBranch']);
}
return $html;
}
/**
* Renders a menu with all the siblings
*
* @param Zend_Navigation_Container $container container to render
* @param string $ulClass CSS class for first UL
* @param string $indent initial indentation
* @param int|null $minDepth minimum depth
* @param int|null $maxDepth maximum depth
* @param bool $onlyActive render only active branch?
* @param bool $renderParentSiblings render all parent siblings
* @return string
*/
protected function _renderMenuWithParentSiblings(Zend_Navigation_Container $container,
$ulClass,
$indent,
$minDepth,
$maxDepth,
$onlyActive,
$renderParentSiblings)
{
$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;
$siblings = array();
if($foundPage) {
foreach ($iterator as $page) {
$depth = $iterator->getDepth();
if($depth >= $minDepth) {
if($page->hasPage($foundPage, true) || $page == $foundPage) {
if(!is_null($page->getParent())) {
foreach ($page->getParent()->getPages() as $sibling) {
$siblings[] = $sibling;
}
}
}
}
}
}
foreach ($iterator as $page) {
$depth = $iterator->getDepth();
/* @var $page Zend_Navigation_Page */
$isActive = $page->isActive(true);
if(in_array($page, $siblings) && $this->accept($page, true) && $depth >= $minDepth) {
$accept = true;
} else if ($depth < $minDepth || !$this->accept($page, true)) {
// page is below minDepth or not accepted by acl/visibilty
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 (!is_null($foundPage->getParent()) && $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(' ', $depth);
if ($depth > $prevDepth) {
// start new ul tag
if ($ulClass && $depth == 0) {
$ulClass = ' class="' . $ulClass . '"';
} else {
$ulClass = '';
}
$html .= $myIndent . '<ul' . $ulClass . '>' . self::EOL;
} else if ($prevDepth > $depth) {
// close li/ul tags until we're at current depth
for ($i = $prevDepth; $i > $depth; $i--) {
$ind = $indent . str_repeat(' ', $i);
$html .= $ind . ' </li>' . self::EOL;
$html .= $ind . '</ul>' . self::EOL;
}
// close previous li tag
$html .= $myIndent . ' </li>' . self::EOL;
} else {
// close previous li tag
$html .= $myIndent . ' </li>' . self::EOL;
}
// render li tag and page
$liClass = $isActive ? ' class="active"' : '';
$html .= $myIndent . ' <li' . $liClass . '>' . self::EOL
. $myIndent . ' ' . $this->htmlify($page) . self::EOL;
// 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(' ', $i-1);
$html .= $myIndent . ' </li>' . self::EOL
. $myIndent . '</ul>' . self::EOL;
}
$html = rtrim($html, self::EOL);
}
return $html;
}
/**
* OVERLOAD - invisible pages should be found back !!
*
* Finds the deepest active page in the given container
*
* @param Zend_Navigation_Container $container container to search
* @param int|null $minDepth [optional] minimum depth
* required for page to be
* valid. Default is to use
* {@link getMinDepth()}. A
* null value means no minimum
* depth required.
* @param int|null $minDepth [optional] maximum depth
* a page can have to be
* valid. Default is to use
* {@link getMaxDepth()}. A
* null value means no maximum
* depth required.
* @return array an associative array with
* the values 'depth' and
* 'page', or an empty array
* if not found
*/
public function findActive(Zend_Navigation_Container $container,
$minDepth = null,
$maxDepth = -1)
{
if (!is_int($minDepth)) {
$minDepth = $this->getMinDepth();
}
if ((!is_int($maxDepth) || $maxDepth < 0) && null !== $maxDepth) {
$maxDepth = $this->getMaxDepth();
}
$found = null;
$foundDepth = -1;
$iterator = new RecursiveIteratorIterator($container,
RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iterator as $page) {
$currDepth = $iterator->getDepth();
// if ($currDepth < $minDepth || !$this->accept($page)) {
// hidden pages should be found
if ($currDepth < $minDepth) {
// page is not accepted
continue;
}
if ($page->isActive(false) && $currDepth > $foundDepth) {
// found an active page at a deeper level than before
$found = $page;
$foundDepth = $currDepth;
}
}
if (is_int($maxDepth) && $foundDepth > $maxDepth) {
while ($foundDepth > $maxDepth) {
if (--$foundDepth < $minDepth) {
$found = null;
break;
}
$found = $found->getParent();
if (!$found instanceof Zend_Navigation_Page) {
$found = null;
break;
}
}
}
if ($found) {
return array('page' => $found, 'depth' => $foundDepth);
} else {
return array();
}
}
}