diff --git a/application/controllers/LayoutController.php b/application/controllers/LayoutController.php
index 929c64109..2d1abfb72 100644
--- a/application/controllers/LayoutController.php
+++ b/application/controllers/LayoutController.php
@@ -2,6 +2,7 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
+use Icinga\Web\MenuRenderer;
use Icinga\Web\Controller\ActionController;
use Icinga\Web\Hook;
use Icinga\Web\Menu;
@@ -17,9 +18,7 @@ class LayoutController extends ActionController
*/
public function menuAction()
{
- $this->view->url = Url::fromRequest()->getRelativeUrl();
- $this->view->items = Menu::fromConfig()->getChildren();
- $this->view->sub = false;
+ $this->view->menuRenderer = new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl());
}
/**
diff --git a/application/layouts/scripts/parts/menu.phtml b/application/layouts/scripts/parts/menu.phtml
deleted file mode 100644
index 7a4f84ef3..000000000
--- a/application/layouts/scripts/parts/menu.phtml
+++ /dev/null
@@ -1,33 +0,0 @@
-level) {
- $this->level = 0;
-}
-?>
-
level === 0 ? ' role="navigation"' : '' ?>>
-items as $item) {
-
- printf(
- ' - %s%s',
- $this->href($this->url) === $this->href($item->getUrl()) ? ' class="active"' : '',
- $item->getUrl() ? $this->href($item->getUrl()) : '#',
- $item->getIcon() ? $this->img(
- $item->getIcon(),
- array('class' => 'icon')
- ) . ' ' : '',
- $this->escape($item->getTitle())
- );
-
- if ($item->hasChildren()) {
- echo $this->partial(
- 'parts/menu.phtml',
- array('items' => $item->getChildren(), 'url' => $this->url, 'level' => $this->level + 1)
- );
- }
-
- echo "
\n";
-}
-
-?>
-
diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml
index 106296173..c689db54a 100644
--- a/application/layouts/scripts/parts/navigation.phtml
+++ b/application/layouts/scripts/parts/navigation.phtml
@@ -2,22 +2,17 @@
use Icinga\Web\Url;
use Icinga\Web\Menu;
+use Icinga\Web\MenuRenderer;
// Don't render a menu for unauthenticated users unless menu is auth aware
if (! $this->auth()->isAuthenticated()) {
return;
}
-// Current url
-$url = Url::fromRequest()->getRelativeUrl();
-$menu = Menu::fromConfig();
?>
diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml
index 889076fc4..4e825777c 100644
--- a/application/views/scripts/layout/menu.phtml
+++ b/application/views/scripts/layout/menu.phtml
@@ -1,39 +1 @@
-
+= $menuRenderer; ?>
\ No newline at end of file
diff --git a/library/Icinga/Web/MenuRenderer.php b/library/Icinga/Web/MenuRenderer.php
new file mode 100644
index 000000000..950543fcd
--- /dev/null
+++ b/library/Icinga/Web/MenuRenderer.php
@@ -0,0 +1,151 @@
+url = $url;
+ parent::__construct($menu, RecursiveIteratorIterator::CHILD_FIRST);
+ }
+
+ /**
+ * Register the outer ul opening html-tag
+ */
+ public function beginIteration()
+ {
+ $this->tags[] = '';
+ }
+
+ /**
+ * Register the outer ul closing html-tag
+ */
+ public function endIteration()
+ {
+ $this->tags[] = '
';
+ }
+
+ /**
+ * Register a inner ul opening html-tag
+ */
+ public function beginChildren()
+ {
+ // The iterator runs in mode CHILD_FIRST so we need to remember
+ // where to insert the parent's opening html tag once its rendered
+ $parent = $this->getSubIterator(0)->current();
+ $this->tags[$parent->getId() . '_begin'] = null;
+
+ $this->tags[] = '';
+ }
+
+ /**
+ * Register a inner ul closing html-tag
+ */
+ public function endChildren()
+ {
+ $this->tags[] = '
';
+
+ // Remember the position of the parent's closing html-tag
+ $parent = $this->getSubIterator(0)->current();
+ $this->tags[$parent->getId() . '_end'] = null;
+ }
+
+ /**
+ * Render the given child
+ *
+ * @param Menu $child The menu's child to render
+ *
+ * @return string The child rendered as html
+ */
+ public function renderChild(Menu $child)
+ {
+ return sprintf(
+ '%s%s',
+ $child->getUrl() ? Url::fromPath($child->getUrl()) : '#',
+ $child->getIcon() ? ' ' : '',
+ htmlspecialchars($child->getTitle())
+ );
+ }
+
+ /**
+ * Return the menu rendered as html
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $passedActiveChild = false;
+ foreach ($this as $child) {
+ $childIsActive = $this->isActive($child);
+ if ($childIsActive && $this->getDepth() > 0) {
+ $passedActiveChild = true;
+ }
+
+ if ($childIsActive || ($passedActiveChild && $this->getDepth() === 0)) {
+ $passedActiveChild &= $this->getDepth() !== 0;
+ $openTag = '';
+ } else {
+ $openTag = '';
+ }
+ $content = $this->renderChild($child);
+ $closingTag = '';
+
+ if (array_key_exists($child->getId() . '_begin', $this->tags)) {
+ $this->tags[$child->getId() . '_begin'] = $openTag . $content;
+ $this->tags[$child->getId() . '_end'] = $closingTag;
+ } else {
+ $this->tags[] = $openTag . $content . $closingTag;
+ }
+ }
+
+ return implode("\n", $this->tags);
+ }
+
+ /**
+ * @see MenuRenderer::render()
+ */
+ public function __toString()
+ {
+ return $this->render();
+ }
+
+ /**
+ * Return whether the current request url references the child's url
+ *
+ * @param Menu $child The menu's child to check
+ *
+ * @return bool
+ */
+ protected function isActive(Menu $child)
+ {
+ return html_entity_decode(rawurldecode($this->url)) === html_entity_decode(rawurldecode($child->getUrl()));
+ }
+}