From 52b0c8c258e23f29367d5c1932062a7b73ffbb30 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 1 Sep 2015 12:48:45 +0200 Subject: [PATCH 001/225] lib: Add navigation classes (WIP) - Lacks custom renderer functionality - Lacks navigation item priorities - Lacks permission handling refs #5600 --- .../Icinga/Web/Navigation/DropdownItem.php | 21 + library/Icinga/Web/Navigation/Navigation.php | 235 +++++++++ .../Icinga/Web/Navigation/NavigationItem.php | 483 ++++++++++++++++++ .../Web/Navigation/NavigationRenderer.php | 340 ++++++++++++ .../NavigationRendererInterface.php | 122 +++++ .../RecursiveNavigationRenderer.php | 118 +++++ 6 files changed, 1319 insertions(+) create mode 100644 library/Icinga/Web/Navigation/DropdownItem.php create mode 100644 library/Icinga/Web/Navigation/Navigation.php create mode 100644 library/Icinga/Web/Navigation/NavigationItem.php create mode 100644 library/Icinga/Web/Navigation/NavigationRenderer.php create mode 100644 library/Icinga/Web/Navigation/NavigationRendererInterface.php create mode 100644 library/Icinga/Web/Navigation/RecursiveNavigationRenderer.php diff --git a/library/Icinga/Web/Navigation/DropdownItem.php b/library/Icinga/Web/Navigation/DropdownItem.php new file mode 100644 index 000000000..98f153500 --- /dev/null +++ b/library/Icinga/Web/Navigation/DropdownItem.php @@ -0,0 +1,21 @@ +children->setLayout(Navigation::LAYOUT_DROPDOWN); + } +} + diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php new file mode 100644 index 000000000..3ec0fd7de --- /dev/null +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -0,0 +1,235 @@ + + * setLayout(Navigation::LAYOUT_TABS); + * $home = new NavigationItem(); + * $home + * ->setIcon('home') + * ->setLabel('Home'); + * ->setUrl('/home'); + * $logout = new NavigationItem(); + * $logout + * ->setIcon('logout') + * ->setLabel('Logout') + * ->setUrl('/logout'); + * $dropdown = new DropdownItem(); + * $dropdown + * ->setIcon('user') + * ->setLabel('Preferences'); + * $preferences = new NavigationItem(); + * $preferences + * ->setIcon('preferences'); + * ->setLabel('preferences') + * ->setUrl('/preferences'); + * $dropdown->addChild($preferences); + * $navigation + * ->addItem($home) + * ->addItem($logout); + * ->addItem($dropdown); + * echo $navigation + * ->getRenderer() + * ->setCssClass('example-nav') + * ->render(); + */ +class Navigation implements ArrayAccess, Countable, IteratorAggregate +{ + /** + * Flag for dropdown layout + * + * @var int + */ + const LAYOUT_DROPDOWN = 1; + + /** + * Flag for tabs layout + * + * @var int + */ + const LAYOUT_TABS = 2; + + /** + * Navigation items + * + * @var NavigationItem[] + */ + protected $items = array(); + + /** + * Navigation layout + * + * @var int + */ + protected $layout; + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return isset($this->items[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return isset($this->items[$offset]) ? $this->items[$offset] : null; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->items[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->items); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Ad a navigation item + * + * @param NavigationItem|array $item The item to append + * + * @return $this + * @throws InvalidArgumentException If the item argument is invalid + */ + public function addItem(NavigationItem $item) + { + if (! $item instanceof NavigationItem) { + if (! is_array($item)) { + throw new InvalidArgumentException( + 'Argument item must be either an array or an instance of NavigationItem' + ); + } + $item = new NavigationItem($item); + } + $this->items[] = $item; + return $this; + } + + /** + * Get the item with the given ID + * + * @param mixed $id + * @param mixed $default + * + * @return NavigationItem|mixed + */ + public function getItem($id, $default = null) + { + if (isset($this->items[$id])) { + return $this->items[$id]; + } + return $default; + } + + /** + * Get the items + * + * @return array + */ + public function getItems() + { + return $this->items; + } + + /** + * Get whether the navigation has items + * + * @return bool + */ + public function hasItems() + { + return ! empty($this->items); + } + + /** + * Get whether the navigation is empty + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Get the layout + * + * @return int + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Set the layout + * + * @param int $layout + * + * @return $this + */ + public function setLayout($layout) + { + $this->layout = (int) $layout; + return $this; + } + + /** + * Get the navigation renderer + * + * @return RecursiveNavigationRenderer + */ + public function getRenderer() + { + return new RecursiveNavigationRenderer($this); + } +} + diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php new file mode 100644 index 000000000..4bc0936cc --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -0,0 +1,483 @@ +setProperties($properties); + } + $this->children = new Navigation(); + $this->init(); + } + + /** + * Initialize the navigation item + */ + public function init() + { + + } + + /** + * {@inheritdoc} + */ + public function count() + { + return $this->children->count(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return $this->children; + } + + /** + * Get whether the item is active + * + * @return bool + */ + public function getActive() + { + return $this->active; + } + + /** + * Set whether the item is active + * + * Bubbles active state. + * + * @param bool $active + * + * @return $this + */ + public function setActive($active = true) + { + $this->active = (bool) $active; + $parent = $this; + while (($parent = $parent->parent) !== null) { + $parent->setActive($active); + } + return $this; + } + + /** + * Get an attribute's value of the item's element + * + * @param string $name Name of the attribute + * @param mixed $default Default value + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + $name = (string) $name; + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + return $default; + } + + /** + * Set an attribute of the item's element + * + * @param string $name Name of the attribute + * @param mixed $value Value of the attribute + * + * @return $this + */ + public function setAttribute($name, $value) + { + $name = (string) $name; + $this->attributes[$name] = $value; + return $this; + } + + /** + * Get the item's attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the item's attributes + * + * @param array $attributes + * + * @return $this + */ + public function setAttributes(array $attributes) + { + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + return $this; + } + + /** + * Add a child item to this item + * + * Bubbles active state. + * + * @param NavigationItem|array $child The child to add + * + * @return $this + * @throws InvalidArgumentException If the child argument is invalid + */ + public function addChild($child) + { + if (! $child instanceof NavigationItem) { + if (! is_array($child)) { + throw new InvalidArgumentException( + 'Argument child must be either an array or an instance of NavigationItem' + ); + } + $child = new NavigationItem($child); + } + $child->parent = $this; + $this->children->addItem($child); + if ($child->getActive()) { + $this->setActive(); + } + return $this; + } + + /** + * Get the item's children + * + * @return Navigation + */ + public function getChildren() + { + return $this->children; + } + + /** + * Get whether the item has children + * + * @return bool + */ + public function hasChildren() + { + return ! $this->children->isEmpty(); + } + + /** + * Set children + * + * @param Navigation $children + * + * @return $this + */ + public function setChildren(Navigation $children) + { + $this->children = $children; + return $this; + } + + /** + * Get the icon + * + * @return string|null + */ + public function getIcon() + { + return $this->icon; + } + + /** + * Set the icon + * + * @param string $icon + * + * @return $this + */ + public function setIcon($icon) + { + $this->icon = (string) $icon; + return $this; + } + + /** + * Get the item's ID + * + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * Set the item's ID + * + * @param mixed $id ID of the item + * + * @return $this + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Get the label + * + * @return string|null + */ + public function getLabel() + { + return $this->label; + } + + /** + * Set the label + * + * @param string $label + * + * @return $this + */ + public function setLabel($label) + { + $this->label = (string) $label; + return $this; + } + + /** + * Get the URL + * + * @return Url|null + */ + public function getUrl() + { + if ($this->url !== null && ! $this->url instanceof Url) { + $this->url = Url::fromPath((string) $this->url); + } + return $this->url; + } + + /** + * Set the URL + * + * @param Url|string $url + * + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * Get the URL parameters + * + * @return array + */ + public function getUrlParameters() + { + return $this->urlParameters; + } + + /** + * Set the URL parameters + * + * @param array $urlParameters + * + * @return $this + */ + public function setUrlParameters(array $urlParameters) + { + $this->urlParameters = $urlParameters; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Set properties for the item + * + * @param array $properties + * + * @return $this + */ + public function setProperties(array $properties = array()) + { + foreach ($properties as $name => $value) { + $setter = 'set' . ucfirst($name); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } + return $this; + } + + /** + * Render the navigation item to HTML + * + * @return string + */ + public function render() + { + $view = $this->getView(); + $label = $view->escape($this->getLabel()); + if (null !== $icon = $this->getIcon()) { + $label = $view->icon($icon) . $label; + } + if (null !== $url = $this->getUrl()) { + $content = sprintf( + '%s', + $view->propertiesToString($this->getAttributes()), + $view->url($url, $this->getUrlParameters()), + $label + ); + } else { + $content = sprintf( + '<%1$s%2$s>%3$s', + static::LINK_ALTERNATIVE, + $view->propertiesToString($this->getAttributes()), + $label + ); + } + return $content; + } + + /** + * Render the navigation item to HTML + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/library/Icinga/Web/Navigation/NavigationRenderer.php b/library/Icinga/Web/Navigation/NavigationRenderer.php new file mode 100644 index 000000000..60cfcf900 --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationRenderer.php @@ -0,0 +1,340 @@ +iterator = $navigation->getIterator(); + $this->navigation = $navigation; + $this->flags = $flags; + } + + /** + * {@inheritdoc} + */ + public function getChildren() + { + return new static($this->current()->getChildren(), $this->flags & static::NAV_DISABLE); + } + + /** + * {@inheritdoc} + */ + public function hasChildren() + { + return $this->current()->hasChildren(); + } + + /** + * {@inheritdoc} + * @return \Icinga\Web\Navigation\NavigationItem + */ + public function current() + { + return $this->iterator->current(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->iterator->key(); + } + + /** + * {@inheritdoc} + */ + public function next() + { + $this->iterator->next(); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->iterator->rewind(); + if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $this->inIteration) { + $this->content[] = $this->beginMarkup(); + $this->inIteration = true; + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + $valid = $this->iterator->valid(); + if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $valid && $this->inIteration) { + $this->content[] = $this->endMarkup(); + $this->inIteration = false; + } + return $valid; + } + + /** + * Begin navigation markup + * + * @return string + */ + public function beginMarkup() + { + $content = array(); + if ($this->flags & static::NAV_MAJOR) { + $el = 'nav'; + } else { + $el = 'div'; + } + if (($elCssClass = $this->getCssClass()) !== null) { + $elCss = ' class="' . $elCssClass . '"'; + } else { + $elCss = ''; + } + $content[] = sprintf( + '<%s%s role="navigation">', + $el, + $elCss + ); + $content[] = sprintf( + '%2$s', + static::HEADING_RANK, + $this->getView()->escape($this->getHeading()) + ); + $content[] = $this->beginChildrenMarkup(); + return implode("\n", $content); + } + + /** + * End navigation markup + * + * @return string + */ + public function endMarkup() + { + $content = array(); + $content[] = $this->endChildrenMarkup(); + if ($this->flags & static::NAV_MAJOR) { + $content[] = ''; + } else { + $content[] = ''; + } + return implode("\n", $content); + } + + /** + * Begin children markup + * + * @return string + */ + public function beginChildrenMarkup() + { + $ulCssClass = static::CSS_CLASS_NAV; + if ($this->navigation->getLayout() & Navigation::LAYOUT_TABS) { + $ulCssClass .= ' ' . static::CSS_CLASS_NAV_TABS; + } + if ($this->navigation->getLayout() & Navigation::LAYOUT_DROPDOWN) { + $ulCssClass .= ' ' . static::CSS_CLASS_NAV_DROPDOWN; + } + return ''; + } + + /** + * Begin item markup + * + * @param NavigationItem $item + * + * @return string + */ + public function beginItemMarkup(NavigationItem $item) + { + $cssClass = array(); + if ($item->getActive()) { + $cssClass[] = static::CSS_CLASS_ACTIVE; + } + if ($item->hasChildren() + && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN + ) { + $cssClass[] = static::CSS_CLASS_DROPDOWN; + $item + ->setAttribute('class', static::CSS_CLASS_DROPDOWN_TOGGLE) + ->setIcon(static::DROPDOWN_TOGGLE_ICON) + ->setUrl('#'); + } + if (! empty($cssClass)) { + $content = sprintf('
  • ', implode(' ', $cssClass)); + } else { + $content = '
  • '; + } + return $content; + } + + /** + * End item markup + * + * @return string + */ + public function endItemMarkup() + { + return '
  • '; + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->cssClass; + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->cssClass = trim((string) $class); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->heading; + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->heading = (string) $heading; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = Icinga::app()->getViewRenderer()->view; + } + return $this->view; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * {@inheritdoc} + */ + public function render() + { + foreach ($this as $navigationItem) { + /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ + $this->content[] = $this->beginItemMarkup($navigationItem); + $this->content[] = $navigationItem->render(); + $this->content[] = $this->endItemMarkup(); + } + return implode("\n", $this->content); + } +} diff --git a/library/Icinga/Web/Navigation/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/NavigationRendererInterface.php new file mode 100644 index 000000000..c3e014f44 --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationRendererInterface.php @@ -0,0 +1,122 @@ +content[] = $this->getInnerIterator()->beginMarkup(); + } + + /** + * {@inheritdoc} + */ + public function endIteration() + { + $this->content[] = $this->getInnerIterator()->endMarkup(); + } + + /** + * {@inheritdoc} + */ + public function beginChildren() + { + $this->content[] = $this->getInnerIterator()->beginChildrenMarkup(); + } + + /** + * {@inheritdoc} + */ + public function endChildren() + { + $this->content[] = $this->getInnerIterator()->endChildrenMarkup(); + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->getInnerIterator()->getCssClass(); + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->getInnerIterator()->setCssClass($class); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->getInnerIterator()->getHeading(); + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->getInnerIterator()->setHeading($heading); + return $this; + } + + /** + * {@inheritdoc} + */ + public function render() + { + foreach ($this as $navigationItem) { + /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ + $this->content[] = $this->getInnerIterator()->beginItemMarkup($navigationItem); + $this->content[] = $navigationItem->render(); + if (! $navigationItem->hasChildren()) { + $this->content[] = $this->getInnerIterator()->endItemMarkup(); + } + } + return implode("\n", $this->content); + } +} From c154f96d44aa7bb5c67cb5def86be543facbe1b5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 1 Sep 2015 16:02:44 +0200 Subject: [PATCH 002/225] Navigation: Add method createItem() refs #5600 --- library/Icinga/Util/String.php | 2 +- library/Icinga/Web/Navigation/Navigation.php | 77 +++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Util/String.php b/library/Icinga/Util/String.php index 712114ee2..3ab09bf1c 100644 --- a/library/Icinga/Util/String.php +++ b/library/Icinga/Util/String.php @@ -24,7 +24,7 @@ class String /** * Uppercase the first character of each word in a string * - * Converts 'first_name' to 'firstName' for example. + * Converts 'first_name' to 'FirstName' for example. * * @param string $name * @param string $separator Word separator diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 3ec0fd7de..da86be10d 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -8,6 +8,11 @@ use ArrayIterator; use Countable; use InvalidArgumentException; use IteratorAggregate; +use Icinga\Application\Icinga; +use Icinga\Application\Logger; +use Icinga\Data\ConfigObject; +use Icinga\Exception\ProgrammingError; +use Icinga\Util\String; /** * Container for navigation items @@ -55,6 +60,13 @@ use IteratorAggregate; */ class Navigation implements ArrayAccess, Countable, IteratorAggregate { + /** + * The class namespace where to locate navigation type classes + * + * @var string + */ + const NAVIGATION_NS = 'Web\\Navigation'; + /** * Flag for dropdown layout * @@ -69,6 +81,13 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ const LAYOUT_TABS = 2; + /** + * Known navigation types + * + * @var array + */ + protected static $types; + /** * Navigation items * @@ -132,7 +151,63 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } /** - * Ad a navigation item + * Create and return a new navigation item for the given configuration + * + * @param string $name + * @param array|ConfigObject $properties + * + * @return NavigationItem + * + * @throws InvalidArgumentException If the $properties argument is neither an array nor a ConfigObject + */ + public function createItem($name, $properties) + { + if ($properties instanceof ConfigObject) { + $properties = $properties->toArray(); + } elseif (! is_array($properties)) { + throw new InvalidArgumentException('Argument $properties must be of type array or ConfigObject'); + } + + $itemType = isset($properties['type']) ? String::cname($properties['type'], '-') : 'NavigationItem'; + if (! empty($this->types) && isset($this->types[$itemType])) { + return new $this->types[$itemType]($name, $properties); + } + + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::NAVIGATION_NS . '\\' . $itemType; + if (class_exists($classPath)) { + $item = $classPath($name, $properties); + break; + } + } + + if ($item === null) { + $classPath = 'Icinga\\' . static::NAVIGATION_NS . '\\' . $itemType; + if (class_exists($classPath)) { + $item = new $classPath($name, $properties); + } + } + + if ($item === null) { + Logger::debug( + 'Failed to find custom navigation item class %s for item %s. Using base class NavigationItem now', + $itemType, + $name + ); + + $item = new NavigationItem($name, $properties); + $this->types[$itemType] = 'Icinga\\Web\\Navigation\\NavigationItem'; + } elseif (! $item instanceof NavigationItem) { + throw new ProgrammingError('Class %s must inherit from NavigationItem', $classPath); + } else { + $this->types[$itemType] = $classPath; + } + + return $item; + } + + /** + * Add a navigation item * * @param NavigationItem|array $item The item to append * From 0788041c4346c70b5c979c5ca62cf7f23928c05c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 12:57:14 +0200 Subject: [PATCH 003/225] Navigation: Accept item configuration for method addItem() And check an item's permission. refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 36 ++++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index da86be10d..e4059629d 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -8,6 +8,7 @@ use ArrayIterator; use Countable; use InvalidArgumentException; use IteratorAggregate; +use Icinga\Authentication\Auth; use Icinga\Application\Icinga; use Icinga\Application\Logger; use Icinga\Data\ConfigObject; @@ -209,23 +210,36 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate /** * Add a navigation item * - * @param NavigationItem|array $item The item to append + * If you do not pass an instance of NavigationItem, this will only add the item + * if it does not require a permission or the current user has the permission. * - * @return $this - * @throws InvalidArgumentException If the item argument is invalid + * @param string|NavigationItem $name The name of the item or an instance of NavigationItem + * @param array $properties The properties of the item to add (Ignored if $name is not a string) + * + * @return bool Whether the item was added or not + * + * @throws InvalidArgumentException In case $name is neither a string nor an instance of NavigationItem */ - public function addItem(NavigationItem $item) + public function addItem($name, array $properties = array()) { - if (! $item instanceof NavigationItem) { - if (! is_array($item)) { - throw new InvalidArgumentException( - 'Argument item must be either an array or an instance of NavigationItem' - ); + if (is_string($name)) { + if (isset($properties['permission'])) { + if (! Auth::getInstance()->hasPermission($properties['permission'])) { + return false; + } + + unset($properties['permission']); } - $item = new NavigationItem($item); + + $item = $this->createItem($name, $properties); + } elseif (! $name instanceof NavigationItem) { + throw new InvalidArgumentException('Argument $name must be of type string or NavigationItem'); + } else { + $item = $name; } + $this->items[] = $item; - return $this; + return true; } /** From 997b57834cb188344515b3c14b0fb48737b3d845 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 12:57:49 +0200 Subject: [PATCH 004/225] Navigation: Add method fromArray() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index e4059629d..a7c1f46b3 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -320,5 +320,22 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate { return new RecursiveNavigationRenderer($this); } + + /** + * Create and return a new set of navigation items for the given array + * + * @param array $array + * + * @return Navigation + */ + public static function fromArray(array $array) + { + $navigation = new static(); + foreach ($array as $name => $properties) { + $navigation->addItem($name, $properties); + } + + return $navigation; + } } From 83974b7698faf459d0112ccf0cf02b07242323e1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 12:58:12 +0200 Subject: [PATCH 005/225] Navigation: Add method fromConfig() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index a7c1f46b3..bad58e390 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -9,9 +9,11 @@ use Countable; use InvalidArgumentException; use IteratorAggregate; use Icinga\Authentication\Auth; +use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Application\Logger; use Icinga\Data\ConfigObject; +use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; use Icinga\Util\String; @@ -321,6 +323,40 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return new RecursiveNavigationRenderer($this); } + /** + * Create and return a new set of navigation items for the given configuration + * + * @param Config $config + * + * @return Navigation + * + * @throws ConfigurationError In case a referenced parent does not exist + */ + public static function fromConfig(Config $config) + { + $flattened = $topLevel = array(); + foreach ($config as $sectionName => $sectionConfig) { + if (! $sectionConfig->parent) { + $topLevel[$sectionName] = $sectionConfig->toArray(); + $flattened[$sectionName] = & $topLevel[$sectionName]; + } elseif (isset($flattened[$sectionConfig->parent])) { + $flattened[$sectionConfig->parent]['children'][$sectionName] = $sectionConfig->toArray(); + $flattened[$sectionName] = & $flattened[$sectionConfig->parent]['children'][$sectionName]; + } else { + throw new ConfigurationError( + t( + 'Failed to add navigation item "%s". Parent "%s" not found. Make' + . ' sure that the parent is defined prior to its child(s).' + ), + $sectionName, + $sectionConfig->parent + ); + } + } + + return static::fromArray($topLevel); + } + /** * Create and return a new set of navigation items for the given array * From b3159ee60dbb93f7dc93cbe1411bfdc70b0eb832 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 12:59:05 +0200 Subject: [PATCH 006/225] NavigationItem: Accept arrays for method setChildren() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 4bc0936cc..b7a57f449 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -269,12 +269,18 @@ class NavigationItem implements Countable, IteratorAggregate /** * Set children * - * @param Navigation $children + * @param array|Navigation $children * * @return $this */ - public function setChildren(Navigation $children) + public function setChildren($children) { + if (is_array($children)) { + $children = Navigation::fromArray($children); + } elseif (! $children instanceof Navigation) { + throw new InvalidArgumentException('Argument $children must be of type array or Navigation'); + } + $this->children = $children; return $this; } From f449c78dbf637ced97974b7fbe08083114638706 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 13:27:12 +0200 Subject: [PATCH 007/225] NavigationItem: Make it having a name instead of a id.. ..and require it as first argument on construction time. refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 80 +++++-------------- .../Icinga/Web/Navigation/NavigationItem.php | 31 +++---- 2 files changed, 35 insertions(+), 76 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index bad58e390..5eff8e37c 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -8,10 +8,10 @@ use ArrayIterator; use Countable; use InvalidArgumentException; use IteratorAggregate; -use Icinga\Authentication\Auth; use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Application\Logger; +use Icinga\Authentication\Auth; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; @@ -19,47 +19,6 @@ use Icinga\Util\String; /** * Container for navigation items - * - * Usage example: - * - * setLayout(Navigation::LAYOUT_TABS); - * $home = new NavigationItem(); - * $home - * ->setIcon('home') - * ->setLabel('Home'); - * ->setUrl('/home'); - * $logout = new NavigationItem(); - * $logout - * ->setIcon('logout') - * ->setLabel('Logout') - * ->setUrl('/logout'); - * $dropdown = new DropdownItem(); - * $dropdown - * ->setIcon('user') - * ->setLabel('Preferences'); - * $preferences = new NavigationItem(); - * $preferences - * ->setIcon('preferences'); - * ->setLabel('preferences') - * ->setUrl('/preferences'); - * $dropdown->addChild($preferences); - * $navigation - * ->addItem($home) - * ->addItem($logout); - * ->addItem($dropdown); - * echo $navigation - * ->getRenderer() - * ->setCssClass('example-nav') - * ->render(); */ class Navigation implements ArrayAccess, Countable, IteratorAggregate { @@ -92,14 +51,14 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate protected static $types; /** - * Navigation items + * This navigation's items * * @var NavigationItem[] */ protected $items = array(); /** - * Navigation layout + * This navigation's layout * * @var int */ @@ -240,30 +199,27 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate $item = $name; } - $this->items[] = $item; + $this->items[$item->getName()] = $item; return true; } /** - * Get the item with the given ID + * Return the item with the given name * - * @param mixed $id + * @param string $name * @param mixed $default * * @return NavigationItem|mixed */ - public function getItem($id, $default = null) + public function getItem($name, $default = null) { - if (isset($this->items[$id])) { - return $this->items[$id]; - } - return $default; + return isset($this->items[$name]) ? $this->items[$name] : $default; } /** - * Get the items + * Return this navigation's items * - * @return array + * @return array */ public function getItems() { @@ -281,9 +237,9 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } /** - * Get whether the navigation is empty + * Return whether this navigation is empty * - * @return bool + * @return bool */ public function isEmpty() { @@ -291,9 +247,9 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } /** - * Get the layout + * Return this navigation's layout * - * @return int + * @return int */ public function getLayout() { @@ -301,9 +257,9 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } /** - * Set the layout + * Set this navigation's layout * - * @param int $layout + * @param int $layout * * @return $this */ @@ -314,9 +270,9 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } /** - * Get the navigation renderer + * Create and return the renderer for this navigation * - * @return RecursiveNavigationRenderer + * @return RecursiveNavigationRenderer */ public function getRenderer() { diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index b7a57f449..51b1f5225 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -53,11 +53,11 @@ class NavigationItem implements Countable, IteratorAggregate protected $icon; /** - * Item's ID + * This item's name * - * @var mixed + * @var string */ - protected $id; + protected $name; /** * Label @@ -95,15 +95,18 @@ class NavigationItem implements Countable, IteratorAggregate protected $view; /** - * Create a new navigation item + * Create a new NavigationItem * - * @param array $properties + * @param string $name + * @param array $properties */ - public function __construct(array $properties = array()) + public function __construct($name, array $properties = null) { + $this->setName($name); if (! empty($properties)) { $this->setProperties($properties); } + $this->children = new Navigation(); $this->init(); } @@ -309,25 +312,25 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the item's ID + * Return this item's name * - * @return mixed + * @return string */ - public function getId() + public function getName() { - return $this->id; + return $this->name; } /** - * Set the item's ID + * Set this item's name * - * @param mixed $id ID of the item + * @param string $name * * @return $this */ - public function setId($id) + public function setName($name) { - $this->id = $id; + $this->name = $name; return $this; } From a4c2081f64512117bafb70d73f7bbe1d56ef4ee8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 13:29:07 +0200 Subject: [PATCH 008/225] Navigation: Remove method hasItems() Redundant, as it's expressible with "! $nav->isEmpty()". refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 5eff8e37c..5aa0c9576 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -226,16 +226,6 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return $this->items; } - /** - * Get whether the navigation has items - * - * @return bool - */ - public function hasItems() - { - return ! empty($this->items); - } - /** * Return whether this navigation is empty * From 29413360d91b1078f7e13ad555e1947c74dc4f87 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 13:34:56 +0200 Subject: [PATCH 009/225] Navigation: Add method merge() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 5aa0c9576..48a07ac4d 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -269,6 +269,24 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return new RecursiveNavigationRenderer($this); } + /** + * Merge this navigation with the given one + * + * Any duplicate items of this navigation will be overwritten by the given navigation's items. + * + * @param Navigation $navigation + * + * @return $this + */ + public function merge(Navigation $navigation) + { + foreach ($navigation as $item) { + $this->addItem($item); + } + + return $this; + } + /** * Create and return a new set of navigation items for the given configuration * From 95a3f1c011175f84bae14b0a98cb76f01055e125 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:14:55 +0200 Subject: [PATCH 010/225] Navigation: Clear the parent name from a configured navigation item refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 48a07ac4d..e41c947ec 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -300,12 +300,15 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate { $flattened = $topLevel = array(); foreach ($config as $sectionName => $sectionConfig) { - if (! $sectionConfig->parent) { + $parentName = $sectionConfig->parent; + unset($sectionConfig->parent); + + if (! $parentName) { $topLevel[$sectionName] = $sectionConfig->toArray(); $flattened[$sectionName] = & $topLevel[$sectionName]; - } elseif (isset($flattened[$sectionConfig->parent])) { - $flattened[$sectionConfig->parent]['children'][$sectionName] = $sectionConfig->toArray(); - $flattened[$sectionName] = & $flattened[$sectionConfig->parent]['children'][$sectionName]; + } elseif (isset($flattened[$parentName])) { + $flattened[$parentName]['children'][$sectionName] = $sectionConfig->toArray(); + $flattened[$sectionName] = & $flattened[$parentName]['children'][$sectionName]; } else { throw new ConfigurationError( t( @@ -313,7 +316,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate . ' sure that the parent is defined prior to its child(s).' ), $sectionName, - $sectionConfig->parent + $parentName ); } } From c03109c4c6cb70fe4a42dbd083215c672dbf171c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:16:05 +0200 Subject: [PATCH 011/225] NavigationItem: Catch exceptions thrown in __toString() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 51b1f5225..23e541cf8 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -4,9 +4,11 @@ namespace Icinga\Web\Navigation; use Countable; +use Exception; use InvalidArgumentException; use IteratorAggregate; use Icinga\Application\Icinga; +use Icinga\Exception\IcingaException; use Icinga\Web\View; use Icinga\Web\Url; @@ -451,18 +453,20 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Render the navigation item to HTML + * Return this item rendered to HTML * - * @return string + * @return string */ public function render() { $view = $this->getView(); + $label = $view->escape($this->getLabel()); - if (null !== $icon = $this->getIcon()) { + if (($icon = $this->getIcon()) !== null) { $label = $view->icon($icon) . $label; } - if (null !== $url = $this->getUrl()) { + + if (($url = $this->getUrl()) !== null) { $content = sprintf( '%s', $view->propertiesToString($this->getAttributes()), @@ -477,16 +481,21 @@ class NavigationItem implements Countable, IteratorAggregate $label ); } + return $content; } /** - * Render the navigation item to HTML + * Return this item rendered to HTML * - * @return string + * @return string */ public function __toString() { - return $this->render(); + try { + return $this->render(); + } catch (Exception $e) { + return IcingaException::describe($e); + } } } From 62f7a49a45fd940fc88d093707841de795cbcaea Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:17:26 +0200 Subject: [PATCH 012/225] NavigationItem: Reduce code complexity in method addChild() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 23e541cf8..925982bfd 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -224,30 +224,21 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Add a child item to this item + * Add a child to this item * - * Bubbles active state. + * If the child is active this item gets activated as well. * - * @param NavigationItem|array $child The child to add + * @param NavigationItem $child * * @return $this - * @throws InvalidArgumentException If the child argument is invalid */ - public function addChild($child) + public function addChild(NavigationItem $child) { - if (! $child instanceof NavigationItem) { - if (! is_array($child)) { - throw new InvalidArgumentException( - 'Argument child must be either an array or an instance of NavigationItem' - ); - } - $child = new NavigationItem($child); - } - $child->parent = $this; - $this->children->addItem($child); + $this->getChildren()->addItem($child->setParent($this)); if ($child->getActive()) { $this->setActive(); } + return $this; } From c9050e2f21d38a0d028a8586dcdce6df70a2eaf3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:20:26 +0200 Subject: [PATCH 013/225] NavigationItem: Initialize $children before setting the properties refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 925982bfd..95f7e982b 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -41,7 +41,7 @@ class NavigationItem implements Countable, IteratorAggregate protected $attributes = array(); /** - * Item's children + * This item's children * * @var Navigation */ @@ -105,16 +105,17 @@ class NavigationItem implements Countable, IteratorAggregate public function __construct($name, array $properties = null) { $this->setName($name); + $this->children = new Navigation(); + if (! empty($properties)) { $this->setProperties($properties); } - $this->children = new Navigation(); $this->init(); } /** - * Initialize the navigation item + * Initialize this NavigationItem */ public function init() { @@ -126,7 +127,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function count() { - return $this->children->count(); + return $this->getChildren()->count(); } /** @@ -134,7 +135,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function getIterator() { - return $this->children; + return $this->getChildren(); } /** @@ -253,13 +254,13 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get whether the item has children + * Return whether this item has any children * - * @return bool + * @return bool */ public function hasChildren() { - return ! $this->children->isEmpty(); + return ! $this->getChildren()->isEmpty(); } /** From 5efcb18fa0f548c015b430c5c8f490ee11d02d0f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:21:40 +0200 Subject: [PATCH 014/225] NavigationItem: Set the parent on a child in method setChildren() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 95f7e982b..a94b5fd8c 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -244,9 +244,9 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the item's children + * Return this item's children * - * @return Navigation + * @return Navigation */ public function getChildren() { @@ -264,7 +264,7 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Set children + * Set this item's children * * @param array|Navigation $children * @@ -278,6 +278,10 @@ class NavigationItem implements Countable, IteratorAggregate throw new InvalidArgumentException('Argument $children must be of type array or Navigation'); } + foreach ($children as $item) { + $item->setParent($this); + } + $this->children = $children; return $this; } From 8a29660226f167fb0e8c8747f2784e30c2b3b8f1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:25:10 +0200 Subject: [PATCH 015/225] NavigationItem: Overwrite the property in method setAttributes() A setter sets something instead of patching it. refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index a94b5fd8c..4d4dbe9b4 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -34,11 +34,11 @@ class NavigationItem implements Countable, IteratorAggregate protected $active = false; /** - * Attributes of the item's element + * The attributes of this item's element * * @var array */ - protected $attributes = array(); + protected $attributes; /** * This item's children @@ -168,49 +168,45 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get an attribute's value of the item's element + * Return the value of the given element attribute * - * @param string $name Name of the attribute - * @param mixed $default Default value + * @param string $name + * @param mixed $default * * @return mixed */ public function getAttribute($name, $default = null) { - $name = (string) $name; - if (array_key_exists($name, $this->attributes)) { - return $this->attributes[$name]; - } - return $default; + $attributes = $this->getAttributes(); + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; } /** - * Set an attribute of the item's element + * Set the value of the given element attribute * - * @param string $name Name of the attribute - * @param mixed $value Value of the attribute + * @param string $name + * @param mixed $value * * @return $this */ public function setAttribute($name, $value) { - $name = (string) $name; $this->attributes[$name] = $value; return $this; } /** - * Get the item's attributes + * Return the attributes of this item's element * - * @return array + * @return array */ public function getAttributes() { - return $this->attributes; + return $this->attributes ?: array(); } /** - * Set the item's attributes + * Set the attributes of this item's element * * @param array $attributes * @@ -218,9 +214,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function setAttributes(array $attributes) { - foreach ($attributes as $name => $value) { - $this->setAttribute($name, $value); - } + $this->attributes = $attributes; return $this; } From 1ef4a2f7d06314a9fa2ce26d5d3092514cc1e006 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:26:07 +0200 Subject: [PATCH 016/225] NavigationItem: Do not provide a default for setProperties() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 4d4dbe9b4..90a77a68f 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -425,13 +425,13 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Set properties for the item + * Set this item's properties * * @param array $properties * * @return $this */ - public function setProperties(array $properties = array()) + public function setProperties(array $properties) { foreach ($properties as $name => $value) { $setter = 'set' . ucfirst($name); @@ -439,6 +439,7 @@ class NavigationItem implements Countable, IteratorAggregate $this->$setter($value); } } + return $this; } From e2beb7d0278e68dfb38ec000369b187949750783 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:27:34 +0200 Subject: [PATCH 017/225] NavigationItem: Return the name if no label is set in getLabel() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 90a77a68f..d05229dcb 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -62,9 +62,9 @@ class NavigationItem implements Countable, IteratorAggregate protected $name; /** - * Label + * This item's label * - * @var string|null + * @var string */ protected $label; @@ -327,17 +327,17 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the label + * Return this item's label * - * @return string|null + * @return string */ public function getLabel() { - return $this->label; + return $this->label ?: $this->getName(); } /** - * Set the label + * Set this item's label * * @param string $label * @@ -345,7 +345,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function setLabel($label) { - $this->label = (string) $label; + $this->label = $label; return $this; } From 2bfeb335a5713438dc050097728d8c7dd190d2cd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:30:35 +0200 Subject: [PATCH 018/225] NavigationItem: Add method setParent() and getParent() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index d05229dcb..634bd6e64 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -69,11 +69,11 @@ class NavigationItem implements Countable, IteratorAggregate protected $label; /** - * Parent + * This item's parent * - * @var NavigationItem|null + * @var NavigationItem */ - private $parent; + protected $parent; /** * URL @@ -326,6 +326,29 @@ class NavigationItem implements Countable, IteratorAggregate return $this; } + /** + * Set this item's parent + * + * @param NavigationItem $parent + * + * @return $this + */ + public function setParent(NavigationItem $parent) + { + $this->parent = $parent; + return $this; + } + + /** + * Return this item's parent + * + * @return NavigationItem + */ + public function getParent() + { + return $this->parent; + } + /** * Return this item's label * From 6802c0a9e49c1ca2297aeadea06def07312011a5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:32:37 +0200 Subject: [PATCH 019/225] NavigationItem: Do not disable the complete parent hierarchy.. ..if just a child gets deactivated. refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 634bd6e64..a90700116 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -27,11 +27,11 @@ class NavigationItem implements Countable, IteratorAggregate const LINK_ALTERNATIVE = 'span'; /** - * Whether the item is active + * Whether this item is active * * @var bool */ - protected $active = false; + protected $active; /** * The attributes of this item's element @@ -139,19 +139,19 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get whether the item is active + * Return whether this item is active * - * @return bool + * @return bool */ public function getActive() { - return $this->active; + return $this->active ?: false; } /** - * Set whether the item is active + * Set whether this item is active * - * Bubbles active state. + * If it's active and has a parent, the parent gets activated as well. * * @param bool $active * @@ -160,10 +160,10 @@ class NavigationItem implements Countable, IteratorAggregate public function setActive($active = true) { $this->active = (bool) $active; - $parent = $this; - while (($parent = $parent->parent) !== null) { - $parent->setActive($active); + if ($this->active && $this->getParent() !== null) { + $this->getParent()->setActive(); } + return $this; } From 1788cc9b6a953d7571b7a5ba955f804fbab31403 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:33:45 +0200 Subject: [PATCH 020/225] NavigationItem: Use setView() to initiliaze $view refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index a90700116..5e168677c 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -92,7 +92,7 @@ class NavigationItem implements Countable, IteratorAggregate /** * View * - * @var View|null + * @var View */ protected $view; @@ -422,15 +422,16 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the view + * Return the view * - * @return View + * @return View */ public function getView() { if ($this->view === null) { - $this->view = Icinga::app()->getViewRenderer()->view; + $this->setView(Icinga::app()->getViewRenderer()->view); } + return $this->view; } From 298c4ad38b80273fa3f6202a96a8b1c9ae9b5b58 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:35:08 +0200 Subject: [PATCH 021/225] NavigationItem: Cast a string to Url already in setUrl() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 5e168677c..b67f16e94 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -76,18 +76,18 @@ class NavigationItem implements Countable, IteratorAggregate protected $parent; /** - * URL + * This item's url * - * @var Url|null + * @var Url */ protected $url; /** - * URL parameters + * Additional parameters for this item's url * * @var array */ - protected $urlParameters = array(); + protected $urlParameters; /** * View @@ -373,43 +373,48 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the URL + * Return this item's url * - * @return Url|null + * @return Url */ public function getUrl() { - if ($this->url !== null && ! $this->url instanceof Url) { - $this->url = Url::fromPath((string) $this->url); - } return $this->url; } /** - * Set the URL + * Set this item's url * - * @param Url|string $url + * @param Url|string $url * * @return $this + * + * @throws InvalidArgumentException If the given url is neither of type */ public function setUrl($url) { + if (is_string($url)) { + $url = Url::fromPath($url); + } elseif (! $url instanceof Url) { + throw new InvalidArgumentException('Argument $url must be of type string or Url'); + } + $this->url = $url; return $this; } /** - * Get the URL parameters + * Return all additional parameters for this item's url * - * @return array + * @return array */ public function getUrlParameters() { - return $this->urlParameters; + return $this->urlParameters ?: array(); } /** - * Set the URL parameters + * Set additional parameters for this item's url * * @param array $urlParameters * From 1393db687bd66d431df653f4c578a6aee343c68c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Sep 2015 15:42:54 +0200 Subject: [PATCH 022/225] NavigationItem: Adjust some docblocks refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index b67f16e94..21cafa4a5 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -14,13 +14,11 @@ use Icinga\Web\Url; /** * A navigation item - * - * @see \Icinga\Web\Navigation\Navigation For a usage example. */ class NavigationItem implements Countable, IteratorAggregate { /** - * Alternative markup element if the navigation item has no URL + * Alternative markup element for items without a url * * @var string */ @@ -48,9 +46,9 @@ class NavigationItem implements Countable, IteratorAggregate protected $children; /** - * Icon + * This item's icon * - * @var string|null + * @var string */ protected $icon; @@ -254,7 +252,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function hasChildren() { - return ! $this->getChildren()->isEmpty(); + return !$this->getChildren()->isEmpty(); } /** @@ -281,9 +279,9 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Get the icon + * Return this item's icon * - * @return string|null + * @return string */ public function getIcon() { @@ -291,7 +289,7 @@ class NavigationItem implements Countable, IteratorAggregate } /** - * Set the icon + * Set this item's icon * * @param string $icon * @@ -299,7 +297,7 @@ class NavigationItem implements Countable, IteratorAggregate */ public function setIcon($icon) { - $this->icon = (string) $icon; + $this->icon = $icon; return $this; } From f4a6ab73c01532dd372509b2d74c7605ee8ad00d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 08:33:13 +0200 Subject: [PATCH 023/225] NavigationItem: Add property priority refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 21cafa4a5..22656bc42 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -31,6 +31,15 @@ class NavigationItem implements Countable, IteratorAggregate */ protected $active; + /** + * This item's priority + * + * The priority defines when the item is rendered in relation to its parent's childs. + * + * @var int + */ + protected $priority; + /** * The attributes of this item's element * @@ -165,6 +174,29 @@ class NavigationItem implements Countable, IteratorAggregate return $this; } + /** + * Return this item's priority + * + * @return int + */ + public function getPriority() + { + return $this->priority; + } + + /** + * Set this item's priority + * + * @param int $priority + * + * @return $this + */ + public function setPriority($priority) + { + $this->priority = (int) $priority; + return $this; + } + /** * Return the value of the given element attribute * From 00447488ee02aedd431d77e227cdd3881d5ddb56 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 08:49:30 +0200 Subject: [PATCH 024/225] NavigationItem: Drop interface Countable $item->count(), count of what? $item->getChildren()->count(), Its children, of course! refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 22656bc42..2a891c45f 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -3,7 +3,6 @@ namespace Icinga\Web\Navigation; -use Countable; use Exception; use InvalidArgumentException; use IteratorAggregate; @@ -15,7 +14,7 @@ use Icinga\Web\Url; /** * A navigation item */ -class NavigationItem implements Countable, IteratorAggregate +class NavigationItem implements IteratorAggregate { /** * Alternative markup element for items without a url @@ -129,14 +128,6 @@ class NavigationItem implements Countable, IteratorAggregate } - /** - * {@inheritdoc} - */ - public function count() - { - return $this->getChildren()->count(); - } - /** * {@inheritdoc} */ From 1fb5c96ef18e6f75688607607e19a2a1dff71e6f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 08:52:02 +0200 Subject: [PATCH 025/225] Navigation: Add method order() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index e41c947ec..6260dbc95 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -109,6 +109,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ public function getIterator() { + $this->order(); return new ArrayIterator($this->items); } @@ -269,6 +270,40 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return new RecursiveNavigationRenderer($this); } + /** + * Order this navigation's items + * + * @return $this + */ + public function order() + { + uasort($this->items, array($this, 'compareItems')); + foreach ($this->items as $item) { + if ($item->hasChildren()) { + $item->getChildren()->order(); + } + } + + return $this; + } + + /** + * Return whether the first item is less than, more than or equal to the second one + * + * @param NavigationItem $a + * @param NavigationItem $b + * + * @return int + */ + protected function compareItems(NavigationItem $a, NavigationItem $b) + { + if ($a->getPriority() === $b->getPriority()) { + return strcasecmp($a->getLabel(), $b->getLabel()); + } + + return $a->getPriority() > $b->getPriority() ? 1 : -1; + } + /** * Merge this navigation with the given one * From a626e8f7fbbd9c118d9fd940a126e361569100d7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 09:56:02 +0200 Subject: [PATCH 026/225] NavigationItem: Allow to set and get single url parameters refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 2a891c45f..23ab3fd86 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -424,6 +424,34 @@ class NavigationItem implements IteratorAggregate return $this; } + /** + * Return the value of the given url parameter + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getUrlParameter($name, $default = null) + { + $parameters = $this->getUrlParameters(); + return isset($parameters[$name]) ? $parameters[$name] : $default; + } + + /** + * Set the value of the given url parameter + * + * @param string $name + * @param mixed $value + * + * @return $this + */ + public function setUrlParameter($name, $value) + { + $this->urlParameters[$name] = $value; + return $this; + } + /** * Return all additional parameters for this item's url * From 537db6000e948195690b099865d95d6c41f37777 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 09:56:22 +0200 Subject: [PATCH 027/225] NavigationItem: Add method merge() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 23ab3fd86..e92561411 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -8,6 +8,7 @@ use InvalidArgumentException; use IteratorAggregate; use Icinga\Application\Icinga; use Icinga\Exception\IcingaException; +use Icinga\Exception\ProgrammingError; use Icinga\Web\View; use Icinga\Web\Url; @@ -521,6 +522,60 @@ class NavigationItem implements IteratorAggregate return $this; } + /** + * Merge this item with the given one + * + * @param NavigationItem $item + * + * @return $this + */ + public function merge(NavigationItem $item) + { + if ($this->conflictsWith($item)) { + throw new ProgrammingError('Cannot merge, conflict detected.'); + } + + if ($item->getActive()) { + $this->setActive(); + } + + if (! $this->getIcon()) { + $this->setIcon($item->getIcon()); + } + + if ($this->getLabel() === $this->getName()) { + $this->setLabel($item->getLabel()); + } + + foreach ($item->getAttributes() as $name => $value) { + $this->setAttribute($name, $value); + } + + foreach ($item->getUrlParameters() as $name => $value) { + $this->setUrlParameter($name, $value); + } + + if ($item->hasChildren()) { + $this->getChildren()->merge($item->getChildren()); + } + } + + /** + * Return whether it's possible to merge this item with the given one + * + * @param NavigationItem $item + * + * @return bool + */ + public function conflictsWith(NavigationItem $item) + { + if ($this->getUrl() === null || $item->getUrl() === null) { + return false; + } + + return $this->getUrl() !== $item->getUrl(); + } + /** * Return this item rendered to HTML * From e6a6ae996a91967f8553df862cfb6962c5bcf61e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 09:57:52 +0200 Subject: [PATCH 028/225] Navigation: Merge other navigations more sophisticated refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 6260dbc95..46c7d71a4 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -316,7 +316,25 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate public function merge(Navigation $navigation) { foreach ($navigation as $item) { - $this->addItem($item); + /** @var $item NavigationItem */ + if (($existingItem = $this->getItem($item->getName())) !== null) { + if ($existingItem->conflictsWith($item)) { + $name = $item->getName(); + do { + if (preg_match('~_(\d+)$~', $name, $matches)) { + $name = preg_replace('~_\d+$~', $matches[1] + 1, $name); + } else { + $name .= '_2'; + } + } while ($this->getItem($name) !== null); + + $this->addItem($item->setName($name)); + } else { + $existingItem->merge($item); + } + } else { + $this->addItem($item); + } } return $this; From 206168672b3d7302e9949fe7c6d6033b547cda80 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 10:22:47 +0200 Subject: [PATCH 029/225] NavigationItem: Add method getUniqueName() refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index e92561411..a4294fa0a 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -325,6 +325,30 @@ class NavigationItem implements IteratorAggregate return $this; } + /** + * Return this item's name escaped with only ASCII chars and/or digits + * + * @return string + */ + protected function getEscapedName() + { + return preg_replace('~[^a-zA-Z0-9]~', '_', $this->getName()); + } + + /** + * Return a unique version of this item's name + * + * @return string + */ + public function getUniqueName() + { + if ($this->getParent() === null) { + return 'navigation-' . $this->getEscapedName(); + } + + return $this->getParent()->getEscapedName() . '-' . $this->getEscapedName(); + } + /** * Return this item's name * From 811269df01480922905634251bc615d5630d8647 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 10:39:11 +0200 Subject: [PATCH 030/225] NavigationItem: Rename method getView() to view() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index a4294fa0a..2c70bf37e 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -505,7 +505,7 @@ class NavigationItem implements IteratorAggregate * * @return View */ - public function getView() + public function view() { if ($this->view === null) { $this->setView(Icinga::app()->getViewRenderer()->view); @@ -607,25 +607,23 @@ class NavigationItem implements IteratorAggregate */ public function render() { - $view = $this->getView(); - - $label = $view->escape($this->getLabel()); + $label = $this->view()->escape($this->getLabel()); if (($icon = $this->getIcon()) !== null) { - $label = $view->icon($icon) . $label; + $label = $this->view()->icon($icon) . $label; } if (($url = $this->getUrl()) !== null) { $content = sprintf( '%s', - $view->propertiesToString($this->getAttributes()), - $view->url($url, $this->getUrlParameters()), + $this->view()->propertiesToString($this->getAttributes()), + $this->view()->url($url, $this->getUrlParameters()), $label ); } else { $content = sprintf( '<%1$s%2$s>%3$s', static::LINK_ALTERNATIVE, - $view->propertiesToString($this->getAttributes()), + $this->view()->propertiesToString($this->getAttributes()), $label ); } From 2834e206d5dc5e02da6927cd59379a697c08543c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 11:25:51 +0200 Subject: [PATCH 031/225] NavigationItem: Add support for custom renderers refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 136 +++++++++++------- .../Renderer/NavigationItemRenderer.php | 81 +++++++++++ 2 files changed, 164 insertions(+), 53 deletions(-) create mode 100644 library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 2c70bf37e..86aa39467 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -9,7 +9,7 @@ use IteratorAggregate; use Icinga\Application\Icinga; use Icinga\Exception\IcingaException; use Icinga\Exception\ProgrammingError; -use Icinga\Web\View; +use Icinga\Web\Navigation\Renderer\NavigationItemRenderer; use Icinga\Web\Url; /** @@ -24,6 +24,11 @@ class NavigationItem implements IteratorAggregate */ const LINK_ALTERNATIVE = 'span'; + /** + * The class namespace where to locate navigation type renderer classes + */ + const RENDERER_NS = 'Web\\Navigation\\Renderer'; + /** * Whether this item is active * @@ -97,11 +102,11 @@ class NavigationItem implements IteratorAggregate protected $urlParameters; /** - * View + * This item's renderer * - * @var View + * @var NavigationItemRenderer */ - protected $view; + protected $renderer; /** * Create a new NavigationItem @@ -500,33 +505,6 @@ class NavigationItem implements IteratorAggregate return $this; } - /** - * Return the view - * - * @return View - */ - public function view() - { - if ($this->view === null) { - $this->setView(Icinga::app()->getViewRenderer()->view); - } - - return $this->view; - } - - /** - * Set the view - * - * @param View $view - * - * @return $this - */ - public function setView(View $view) - { - $this->view = $view; - return $this; - } - /** * Set this item's properties * @@ -600,6 +578,79 @@ class NavigationItem implements IteratorAggregate return $this->getUrl() !== $item->getUrl(); } + /** + * Create and return the given renderer + * + * @param string $name + * + * @return NavigationItemRenderer + */ + protected function createRenderer($name) + { + $renderer = null; + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::RENDERER_NS . '\\' . $name; + if (class_exists($classPath)) { + $renderer = new $classPath(); + break; + } + } + + if ($renderer === null) { + $classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name; + if (class_exists($classPath)) { + $renderer = new $classPath(); + } + } + + if ($renderer === null) { + throw new ProgrammingError( + 'Cannot find renderer "%s" for navigation item "%s"', + $name, + $this->getName() + ); + } elseif (! $renderer instanceof NavigationItemRenderer) { + throw new ProgrammingError('Class %s must inherit from NavigationItemRenderer', $classPath); + } + + return $renderer; + } + + /** + * Set this item's renderer + * + * @param string|NavigationItemRenderer $renderer + * + * @return $this + * + * @throws InvalidArgumentException If the $renderer argument is neither a string nor a NavigationItemRenderer + */ + public function setRenderer($renderer) + { + if (is_string($renderer)) { + $renderer = $this->createRenderer($renderer); + } elseif (! $renderer instanceof NavigationItemRenderer) { + throw new InvalidArgumentException('Argument $renderer must be of type string or NavigationItemRenderer'); + } + + $this->renderer = $renderer; + return $this; + } + + /** + * Return this item's renderer + * + * @return NavigationItemRenderer + */ + public function getRenderer() + { + if ($this->renderer === null) { + $this->setRenderer('NavigationItemRenderer'); + } + + return $this->renderer; + } + /** * Return this item rendered to HTML * @@ -607,28 +658,7 @@ class NavigationItem implements IteratorAggregate */ public function render() { - $label = $this->view()->escape($this->getLabel()); - if (($icon = $this->getIcon()) !== null) { - $label = $this->view()->icon($icon) . $label; - } - - if (($url = $this->getUrl()) !== null) { - $content = sprintf( - '%s', - $this->view()->propertiesToString($this->getAttributes()), - $this->view()->url($url, $this->getUrlParameters()), - $label - ); - } else { - $content = sprintf( - '<%1$s%2$s>%3$s', - static::LINK_ALTERNATIVE, - $this->view()->propertiesToString($this->getAttributes()), - $label - ); - } - - return $content; + $this->getRenderer()->render($this); } /** diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php new file mode 100644 index 000000000..3a9d377e2 --- /dev/null +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -0,0 +1,81 @@ +view === null) { + $this->setView(Icinga::app()->getViewRenderer()->view); + } + + return $this->view; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + + /** + * Render the given navigation item as HTML anchor + * + * @param NavigationItem $item + * + * @return string + */ + public function render(NavigationItem $item) + { + $label = $this->view()->escape($item->getLabel()); + if (($icon = $item->getIcon()) !== null) { + $label = $this->view()->icon($icon) . $label; + } + + if (($url = $item->getUrl()) !== null) { + $content = sprintf( + '%s', + $this->view()->propertiesToString($item->getAttributes()), + $this->view()->url($url, $item->getUrlParameters()), + $label + ); + } else { + $content = sprintf( + '<%1$s%2$s>%3$s', + $item::LINK_ALTERNATIVE, + $this->view()->propertiesToString($item->getAttributes()), + $label + ); + } + + return $content; + } +} From c35db55857b59e48ba40135f7cc88fe09c97d709 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 11:26:27 +0200 Subject: [PATCH 032/225] Navigation: Add missing new keyword refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 46c7d71a4..15bab5a8c 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -139,7 +139,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::NAVIGATION_NS . '\\' . $itemType; if (class_exists($classPath)) { - $item = $classPath($name, $properties); + $item = new $classPath($name, $properties); break; } } From c3ad665c15a7809f9d8c5dc0a7bbe5dded28e3d4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 12:52:07 +0200 Subject: [PATCH 033/225] Navigation: Fix accessing a non-existent and static variable refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 15bab5a8c..24b60ba41 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -132,10 +132,11 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate } $itemType = isset($properties['type']) ? String::cname($properties['type'], '-') : 'NavigationItem'; - if (! empty($this->types) && isset($this->types[$itemType])) { - return new $this->types[$itemType]($name, $properties); + if (! empty(static::$types) && isset(static::$types[$itemType])) { + return new static::$types[$itemType]($name, $properties); } + $item = null; foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::NAVIGATION_NS . '\\' . $itemType; if (class_exists($classPath)) { @@ -159,11 +160,11 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate ); $item = new NavigationItem($name, $properties); - $this->types[$itemType] = 'Icinga\\Web\\Navigation\\NavigationItem'; + static::$types[$itemType] = 'Icinga\\Web\\Navigation\\NavigationItem'; } elseif (! $item instanceof NavigationItem) { throw new ProgrammingError('Class %s must inherit from NavigationItem', $classPath); } else { - $this->types[$itemType] = $classPath; + static::$types[$itemType] = $classPath; } return $item; From 67dee62c5748ec64296e8afce4e3497ae5d97b8e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 13:36:13 +0200 Subject: [PATCH 034/225] NavigationItemRenderer: Add support for options refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 21 +++++++++---- .../Renderer/NavigationItemRenderer.php | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 86aa39467..6f65a3715 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -581,17 +581,24 @@ class NavigationItem implements IteratorAggregate /** * Create and return the given renderer * - * @param string $name + * @param string|array $name * * @return NavigationItemRenderer */ protected function createRenderer($name) { + if (is_array($name)) { + $options = array_splice($name, 1); + $name = $name[0]; + } else { + $options = array(); + } + $renderer = null; foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::RENDERER_NS . '\\' . $name; if (class_exists($classPath)) { - $renderer = new $classPath(); + $renderer = new $classPath($options); break; } } @@ -599,7 +606,7 @@ class NavigationItem implements IteratorAggregate if ($renderer === null) { $classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name; if (class_exists($classPath)) { - $renderer = new $classPath(); + $renderer = new $classPath($options); } } @@ -619,7 +626,7 @@ class NavigationItem implements IteratorAggregate /** * Set this item's renderer * - * @param string|NavigationItemRenderer $renderer + * @param string|array|NavigationItemRenderer $renderer * * @return $this * @@ -627,10 +634,12 @@ class NavigationItem implements IteratorAggregate */ public function setRenderer($renderer) { - if (is_string($renderer)) { + if (is_string($renderer) || is_array($renderer)) { $renderer = $this->createRenderer($renderer); } elseif (! $renderer instanceof NavigationItemRenderer) { - throw new InvalidArgumentException('Argument $renderer must be of type string or NavigationItemRenderer'); + throw new InvalidArgumentException( + 'Argument $renderer must be of type string, array or NavigationItemRenderer' + ); } $this->renderer = $renderer; diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 3a9d377e2..4c872c07f 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -4,6 +4,7 @@ namespace Icinga\Web\Navigation\Renderer; use Icinga\Application\Icinga; +use Icinga\Util\String; use Icinga\Web\Navigation\NavigationItem; use Icinga\Web\View; @@ -19,6 +20,35 @@ class NavigationItemRenderer */ protected $view; + /** + * Create a new NavigationItemRenderer + * + * @param array $options + */ + public function __construct(array $options = null) + { + if (! empty($options)) { + $this->setOptions($options); + } + } + + /** + * Set the given options + * + * @param array $options + * + * @return $this + */ + public function setOptions(array $options) + { + foreach ($options as $name => $value) { + $setter = 'set' . String::cname($name); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } + } + /** * Return the view * From 16ece09b0a290e69e53858f80a90f8d30666b56c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 13:36:34 +0200 Subject: [PATCH 035/225] NavigationItemRenderer: Add option $target refs #5600 --- .../Renderer/NavigationItemRenderer.php | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 4c872c07f..4b56b5f04 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -20,6 +20,13 @@ class NavigationItemRenderer */ protected $view; + /** + * The link target + * + * @var string + */ + protected $target; + /** * Create a new NavigationItemRenderer * @@ -49,6 +56,19 @@ class NavigationItemRenderer } } + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; + } + /** * Return the view * @@ -64,18 +84,28 @@ class NavigationItemRenderer } /** - * Set the view + * Set the link target * - * @param View $view + * @param string $target * * @return $this */ - public function setView(View $view) + public function setTarget($target) { - $this->view = $view; + $this->target = $target; return $this; } + /** + * Return the link target + * + * @return string + */ + public function getTarget() + { + return $this->target; + } + /** * Render the given navigation item as HTML anchor * @@ -92,9 +122,10 @@ class NavigationItemRenderer if (($url = $item->getUrl()) !== null) { $content = sprintf( - '%s', + '%s', $this->view()->propertiesToString($item->getAttributes()), $this->view()->url($url, $item->getUrlParameters()), + $this->target ? ' target="' . $this->view()->escape($this->target) . '"' : '', $label ); } else { From 9a725330fb6a8e5382ac909aeb0c44738f318ed8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 14:22:38 +0200 Subject: [PATCH 036/225] NavigationItem: Provide the item to the renderer before calling render refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 2 +- .../Renderer/NavigationItemRenderer.php | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 6f65a3715..23d86d41b 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -667,7 +667,7 @@ class NavigationItem implements IteratorAggregate */ public function render() { - $this->getRenderer()->render($this); + $this->getRenderer()->setItem($this)->render(); } /** diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 4b56b5f04..00a42d407 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -4,6 +4,7 @@ namespace Icinga\Web\Navigation\Renderer; use Icinga\Application\Icinga; +use Icinga\Exception\ProgrammingError; use Icinga\Util\String; use Icinga\Web\Navigation\NavigationItem; use Icinga\Web\View; @@ -20,6 +21,13 @@ class NavigationItemRenderer */ protected $view; + /** + * The item being rendered + * + * @var NavigationItem + */ + protected $item; + /** * The link target * @@ -83,6 +91,29 @@ class NavigationItemRenderer return $this->view; } + /** + * Set the navigation item to render + * + * @param NavigationItem $item + * + * @return $this + */ + public function setItem(NavigationItem $item) + { + $this->item = $item; + return $this; + } + + /** + * Return the navigation item being rendered + * + * @return NavigationItem + */ + public function getItem() + { + return $this->item; + } + /** * Set the link target * @@ -113,8 +144,17 @@ class NavigationItemRenderer * * @return string */ - public function render(NavigationItem $item) + public function render(NavigationItem $item = null) { + if ($item !== null) { + $this->setItem($item); + } elseif (($item = $this->getItem()) === null) { + throw new ProgrammingError( + 'Cannot render nothing. Pass the item to render as part' + . ' of the call to render() or set it with setItem()' + ); + } + $label = $this->view()->escape($item->getLabel()); if (($icon = $item->getIcon()) !== null) { $label = $this->view()->icon($icon) . $label; From a930ea7f7a52a14ea68958687513a9ed086356e7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 15:04:07 +0200 Subject: [PATCH 037/225] Navigation: Add method render() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 27 +++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 24b60ba41..bcd528b76 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -5,6 +5,7 @@ namespace Icinga\Web\Navigation; use ArrayAccess; use ArrayIterator; +use Exception; use Countable; use InvalidArgumentException; use IteratorAggregate; @@ -14,6 +15,7 @@ use Icinga\Application\Logger; use Icinga\Authentication\Auth; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; +use Icinga\Exception\IcingaException; use Icinga\Exception\ProgrammingError; use Icinga\Util\String; @@ -271,6 +273,16 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return new RecursiveNavigationRenderer($this); } + /** + * Return this navigation rendered to HTML + * + * @return string + */ + public function render() + { + return $this->getRenderer()->render(); + } + /** * Order this navigation's items * @@ -394,5 +406,18 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return $navigation; } -} + /** + * Return this navigation rendered to HTML + * + * @return string + */ + public function __toString() + { + try { + return $this->render(); + } catch (Exception $e) { + return IcingaException::describe($e); + } + } +} From 7adfc588789cbc2961533f92fa367edb9931b9d0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 15:04:39 +0200 Subject: [PATCH 038/225] NavigationItem: Add missing return keyword in method render() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 23d86d41b..ef8b32bfa 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -667,7 +667,7 @@ class NavigationItem implements IteratorAggregate */ public function render() { - $this->getRenderer()->setItem($this)->render(); + return $this->getRenderer()->setItem($this)->render(); } /** From b53fb04a0f75f6c5ce844d6addcc5110b562da9e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 15:52:37 +0200 Subject: [PATCH 039/225] Move navigation related renderer classes to the new namespace refs #5600 --- .../Icinga/Web/Navigation/{ => Renderer}/NavigationRenderer.php | 0 .../Web/Navigation/{ => Renderer}/NavigationRendererInterface.php | 0 .../Web/Navigation/{ => Renderer}/RecursiveNavigationRenderer.php | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename library/Icinga/Web/Navigation/{ => Renderer}/NavigationRenderer.php (100%) rename library/Icinga/Web/Navigation/{ => Renderer}/NavigationRendererInterface.php (100%) rename library/Icinga/Web/Navigation/{ => Renderer}/RecursiveNavigationRenderer.php (100%) diff --git a/library/Icinga/Web/Navigation/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php similarity index 100% rename from library/Icinga/Web/Navigation/NavigationRenderer.php rename to library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php diff --git a/library/Icinga/Web/Navigation/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php similarity index 100% rename from library/Icinga/Web/Navigation/NavigationRendererInterface.php rename to library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php diff --git a/library/Icinga/Web/Navigation/RecursiveNavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php similarity index 100% rename from library/Icinga/Web/Navigation/RecursiveNavigationRenderer.php rename to library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php From ad6a2938ab437c06dbf41def52412227f8bf25b9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 15:53:23 +0200 Subject: [PATCH 040/225] Add class BadgeNavigationItemRenderer refs #5600 --- .../Renderer/BadgeNavigationItemRenderer.php | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php diff --git a/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php new file mode 100644 index 000000000..a5e09f555 --- /dev/null +++ b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php @@ -0,0 +1,118 @@ +title = $title; + return $this; + } + + /** + * Return the tooltip text for the badge + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set the state identifier to use + * + * @param string $state + * + * @return $this + */ + public function setState($state) + { + $this->state = $state; + return $this; + } + + /** + * Return the state identifier to use + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Return the amount of items represented by the badge + * + * @return int + */ + abstract public function getCount(); + + /** + * Render the given navigation item as HTML anchor with a badge + * + * @param NavigationItem $item + * + * @return string + */ + public function render(NavigationItem $item = null) + { + return $this->renderBadge() . parent::render($item); + } + + /** + * Render the badge + * + * @return string + */ + protected function renderBadge() + { + if (($count = $this->getCount()) > 0) { + return sprintf( + '
    %s
    ', + $this->getView()->escape($this->getTitle()), + $this->getView()->escape($this->getState()), + $count + ); + } + + return ''; + } +} From 9e558c98619c865f08c66443cb8aea414ee380d8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 3 Sep 2015 15:53:42 +0200 Subject: [PATCH 041/225] Add class SummaryNavigationItemRenderer refs #5600 --- .../SummaryNavigationItemRenderer.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php diff --git a/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php new file mode 100644 index 000000000..62cd94eb0 --- /dev/null +++ b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php @@ -0,0 +1,46 @@ +getItem()->getChildren() as $child) { + $renderer = $child->getRenderer(); + if ($renderer instanceof BadgeNavigationItemRenderer) { + if ($renderer->getState() === $this->getState()) { + $this->titles[] = $renderer->getTitle(); + $count += $renderer->getCount(); + } + } + } + + return $count; + } + + /** + * {@inheritdoc} + */ + public function getTitle() + { + return join(', ', $this->titles); + } +} From a6b2c23684b39b97693403e1b5fe2fd470db4c6a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:08:20 +0200 Subject: [PATCH 042/225] Update navigation rendering code to fit the previous adjustments refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 1 + .../Renderer/NavigationRenderer.php | 305 ++++++++++-------- .../Renderer/NavigationRendererInterface.php | 68 ++-- .../Renderer/RecursiveNavigationRenderer.php | 133 +++++--- 4 files changed, 285 insertions(+), 222 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index bcd528b76..781eb4e8d 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -18,6 +18,7 @@ use Icinga\Exception\ConfigurationError; use Icinga\Exception\IcingaException; use Icinga\Exception\ProgrammingError; use Icinga\Util\String; +use Icinga\Web\Navigation\Renderer\RecursiveNavigationRenderer; /** * Container for navigation items diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index 60cfcf900..0c47fc0d4 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -1,11 +1,15 @@ skipOuterElement = $skipOuterElement; $this->iterator = $navigation->getIterator(); $this->navigation = $navigation; - $this->flags = $flags; + $this->content = array(); + } + + /** + * {@inheritdoc} + */ + public function setElementTag($tag) + { + $this->elementTag = $tag; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getElementTag() + { + return $this->elementTag ?: static::OUTER_ELEMENT_TAG; + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->cssClass = $class; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->cssClass; + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->heading = $heading; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->heading; + } + + /** + * Return the view + * + * @return View + */ + public function view() + { + if ($this->view === null) { + $this->setView(Icinga::app()->getViewRenderer()->view); + } + + return $this->view; + } + + /** + * Set the view + * + * @param View $view + * + * @return $this + */ + public function setView(View $view) + { + $this->view = $view; + return $this; } /** @@ -87,7 +170,7 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa */ public function getChildren() { - return new static($this->current()->getChildren(), $this->flags & static::NAV_DISABLE); + return new static($this->current()->getChildren(), $this->skipOuterElement); } /** @@ -100,7 +183,8 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa /** * {@inheritdoc} - * @return \Icinga\Web\Navigation\NavigationItem + * + * @return NavigationItem */ public function current() { @@ -129,9 +213,8 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa public function rewind() { $this->iterator->rewind(); - if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $this->inIteration) { + if (! $this->skipOuterElement) { $this->content[] = $this->beginMarkup(); - $this->inIteration = true; } } @@ -141,83 +224,69 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa public function valid() { $valid = $this->iterator->valid(); - if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $valid && $this->inIteration) { + if (! $this->skipOuterElement && !$valid) { $this->content[] = $this->endMarkup(); - $this->inIteration = false; } + return $valid; } /** - * Begin navigation markup + * Return the opening markup for the navigation * - * @return string + * @return string */ public function beginMarkup() { $content = array(); - if ($this->flags & static::NAV_MAJOR) { - $el = 'nav'; - } else { - $el = 'div'; - } - if (($elCssClass = $this->getCssClass()) !== null) { - $elCss = ' class="' . $elCssClass . '"'; - } else { - $elCss = ''; - } $content[] = sprintf( '<%s%s role="navigation">', - $el, - $elCss + $this->getElementTag(), + $this->getCssClass() !== null ? ' class="' . $this->getCssClass() . '"' : '' ); $content[] = sprintf( '%2$s', static::HEADING_RANK, - $this->getView()->escape($this->getHeading()) + $this->view()->escape($this->getHeading()) ); $content[] = $this->beginChildrenMarkup(); - return implode("\n", $content); + return join("\n", $content); } /** - * End navigation markup + * Return the closing markup for the navigation * - * @return string + * @return string */ public function endMarkup() { $content = array(); $content[] = $this->endChildrenMarkup(); - if ($this->flags & static::NAV_MAJOR) { - $content[] = ''; - } else { - $content[] = ''; - } - return implode("\n", $content); + $content[] = 'getElementTag() . '>'; + return join("\n", $content); } /** - * Begin children markup + * Return the opening markup for multiple navigation items * - * @return string + * @return string */ public function beginChildrenMarkup() { - $ulCssClass = static::CSS_CLASS_NAV; - if ($this->navigation->getLayout() & Navigation::LAYOUT_TABS) { - $ulCssClass .= ' ' . static::CSS_CLASS_NAV_TABS; + $cssClass = array(static::CSS_CLASS_NAV); + if ($this->navigation->getLayout() === Navigation::LAYOUT_TABS) { + $cssClass[] = static::CSS_CLASS_NAV_TABS; + } elseif ($this->navigation->getLayout() === Navigation::LAYOUT_DROPDOWN) { + $cssClass[] = static::CSS_CLASS_NAV_DROPDOWN; } - if ($this->navigation->getLayout() & Navigation::LAYOUT_DROPDOWN) { - $ulCssClass .= ' ' . static::CSS_CLASS_NAV_DROPDOWN; - } - return '
      '; + + return '
        '; } /** - * End children markup + * Return the closing markup for multiple navigation items * - * @return string + * @return string */ public function endChildrenMarkup() { @@ -225,7 +294,7 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa } /** - * Begin item markup + * Return the opening markup for the given navigation item * * @param NavigationItem $item * @@ -237,104 +306,58 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa if ($item->getActive()) { $cssClass[] = static::CSS_CLASS_ACTIVE; } - if ($item->hasChildren() - && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN - ) { + + if ($item->hasChildren() && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN) { $cssClass[] = static::CSS_CLASS_DROPDOWN; $item ->setAttribute('class', static::CSS_CLASS_DROPDOWN_TOGGLE) ->setIcon(static::DROPDOWN_TOGGLE_ICON) ->setUrl('#'); } + if (! empty($cssClass)) { - $content = sprintf('
      • ', implode(' ', $cssClass)); + $content = sprintf('
      • ', join(' ', $cssClass)); } else { $content = '
      • '; } + return $content; } /** - * End item markup + * Return the closing markup for a navigation item * - * @return string + * @return string */ public function endItemMarkup() { return '
      • '; } - /** - * {@inheritdoc} - */ - public function getCssClass() - { - return $this->cssClass; - } - - /** - * {@inheritdoc} - */ - public function setCssClass($class) - { - $this->cssClass = trim((string) $class); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHeading() - { - return $this->heading; - } - - /** - * {@inheritdoc} - */ - public function setHeading($heading) - { - $this->heading = (string) $heading; - return $this; - } - - /** - * Get the view - * - * @return View - */ - public function getView() - { - if ($this->view === null) { - $this->view = Icinga::app()->getViewRenderer()->view; - } - return $this->view; - } - - /** - * Set the view - * - * @param View $view - * - * @return $this - */ - public function setView(View $view) - { - $this->view = $view; - return $this; - } - /** * {@inheritdoc} */ public function render() { - foreach ($this as $navigationItem) { - /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ - $this->content[] = $this->beginItemMarkup($navigationItem); - $this->content[] = $navigationItem->render(); + foreach ($this as $item) { + /** @var NavigationItem $item */ + $this->content[] = $this->beginItemMarkup($item); + $this->content[] = $item->render(); $this->content[] = $this->endItemMarkup(); } - return implode("\n", $this->content); + + return join("\n", $this->content); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + try { + return $this->render(); + } catch (Exception $e) { + return IcingaException::describe($e); + } } } diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php index c3e014f44..d9306e29e 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php @@ -1,12 +1,10 @@ content = array(); + parent::__construct( + new NavigationRenderer($navigation, true), + RecursiveIteratorIterator::SELF_FIRST + ); + } + + /** + * {@inheritdoc} + */ + public function setElementTag($tag) + { + $this->getInnerIterator()->setElementTag($tag); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getElementTag() + { + return $this->getInnerIterator()->getElementTag(); + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->getInnerIterator()->setCssClass($class); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->getInnerIterator()->getCssClass(); + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->getInnerIterator()->setHeading($heading); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->getInnerIterator()->getHeading(); } /** @@ -66,53 +122,32 @@ class RecursiveNavigationRenderer extends RecursiveIteratorIterator implements N $this->content[] = $this->getInnerIterator()->endChildrenMarkup(); } - /** - * {@inheritdoc} - */ - public function getCssClass() - { - return $this->getInnerIterator()->getCssClass(); - } - - /** - * {@inheritdoc} - */ - public function setCssClass($class) - { - $this->getInnerIterator()->setCssClass($class); - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHeading() - { - return $this->getInnerIterator()->getHeading(); - } - - /** - * {@inheritdoc} - */ - public function setHeading($heading) - { - $this->getInnerIterator()->setHeading($heading); - return $this; - } - /** * {@inheritdoc} */ public function render() { - foreach ($this as $navigationItem) { - /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ - $this->content[] = $this->getInnerIterator()->beginItemMarkup($navigationItem); - $this->content[] = $navigationItem->render(); - if (! $navigationItem->hasChildren()) { + foreach ($this as $item) { + /** @var NavigationItem $item */ + $this->content[] = $this->getInnerIterator()->beginItemMarkup($item); + $this->content[] = $item->render(); + if (! $item->hasChildren()) { $this->content[] = $this->getInnerIterator()->endItemMarkup(); } } - return implode("\n", $this->content); + + return join("\n", $this->content); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + try { + return $this->render(); + } catch (Exception $e) { + return IcingaException::describe($e); + } } } From a8d5a78aa28be8338115ecad6dee8c473431dce9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:25:25 +0200 Subject: [PATCH 043/225] NavigationItem: Fix method getUniqueName() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index ef8b32bfa..f662ee805 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -351,7 +351,7 @@ class NavigationItem implements IteratorAggregate return 'navigation-' . $this->getEscapedName(); } - return $this->getParent()->getEscapedName() . '-' . $this->getEscapedName(); + return $this->getParent()->getUniqueName() . '-' . $this->getEscapedName(); } /** From c60839d798af23387438770055ac7efa60bfc7e7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:25:58 +0200 Subject: [PATCH 044/225] NavigationItemRenderer: Provide a id if none is set refs #5600 --- .../Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 00a42d407..e942e7078 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -160,6 +160,10 @@ class NavigationItemRenderer $label = $this->view()->icon($icon) . $label; } + if (! $item->getAttribute('id')) { + $item->setAttribute('id', $item->getUniqueName()); + } + if (($url = $item->getUrl()) !== null) { $content = sprintf( '%s', From 4e948b46bf86e7a47729a5b5316b4cfe6e7817af Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:26:28 +0200 Subject: [PATCH 045/225] NavigationRenderer: Add id "navigation" to the navigation's heading refs #5600 --- library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index 0c47fc0d4..cccd2878c 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -245,7 +245,7 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa $this->getCssClass() !== null ? ' class="' . $this->getCssClass() . '"' : '' ); $content[] = sprintf( - '%2$s', + '%2$s', static::HEADING_RANK, $this->view()->escape($this->getHeading()) ); From 975cc92f6bacf3bb7c66cbef95f1b619fcf059cf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:31:23 +0200 Subject: [PATCH 046/225] NavigationItem: Provide "#" as default url for items with children refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index f662ee805..f26b007fd 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -430,6 +430,10 @@ class NavigationItem implements IteratorAggregate */ public function getUrl() { + if ($this->url === null && $this->hasChildren()) { + return '#'; + } + return $this->url; } From 2855778dc7d084cd19a78825acb03298046b73b9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:38:03 +0200 Subject: [PATCH 047/225] Revert "NavigationItemRenderer: Provide a id if none is set" This reverts commit c60839d798af23387438770055ac7efa60bfc7e7. --- .../Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index e942e7078..00a42d407 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -160,10 +160,6 @@ class NavigationItemRenderer $label = $this->view()->icon($icon) . $label; } - if (! $item->getAttribute('id')) { - $item->setAttribute('id', $item->getUniqueName()); - } - if (($url = $item->getUrl()) !== null) { $content = sprintf( '%s', From baefc89f85d45b85976e5544b25d504a3435357f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 09:41:39 +0200 Subject: [PATCH 048/225] NavigationRenderer: Provide a id for outer item markup (
      • ) refs #5600 --- .../Icinga/Web/Navigation/Renderer/NavigationRenderer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index cccd2878c..bfba7d117 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -316,9 +316,13 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa } if (! empty($cssClass)) { - $content = sprintf('
      • ', join(' ', $cssClass)); + $content = sprintf( + '
      • ', + $this->view()->escape($item->getUniqueName()), + join(' ', $cssClass) + ); } else { - $content = '
      • '; + $content = '
      • '; } return $content; From 07588595f284d6093e3e6d2a31752d07047116db Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 10:10:33 +0200 Subject: [PATCH 049/225] NavigationItem: Automatically determine whether it's active or not refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index f26b007fd..9b7e5e8fa 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -149,7 +149,21 @@ class NavigationItem implements IteratorAggregate */ public function getActive() { - return $this->active ?: false; + if ($this->active === null) { + $this->active = false; + if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) { + $this->setActive(); + } elseif ($this->hasChildren()) { + foreach ($this->getChildren() as $item) { + /** @var NavigationItem $item */ + if ($item->getActive()) { + // Do nothing, a true active state is automatically passed to all parents + } + } + } + } + + return $this->active; } /** From 5ff3db8a3c340a49385fb936db7e018788806248 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 10:34:39 +0200 Subject: [PATCH 050/225] Url: Explicitly handle '#' Feels like a quick&dirty solution. Feel free to improve it. refs #5600 --- library/Icinga/Web/Url.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 7c3829537..a46f3666d 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -124,7 +124,7 @@ class Url $request = self::getRequest(); } - if (!is_string($url)) { + if (! is_string($url)) { throw new ProgrammingError( 'url "%s" is not a string', $url @@ -135,6 +135,11 @@ class Url $baseUrl = $request->getBaseUrl(); $urlObject->setBaseUrl($baseUrl); + if ($url === '#') { + $urlObject->setPath($url); + return $urlObject; + } + $urlParts = parse_url($url); if (isset($urlParts['path'])) { if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) { @@ -143,6 +148,7 @@ class Url $urlObject->setPath($urlParts['path']); } } + // TODO: This has been used by former filter implementation, remove it: if (isset($urlParts['query'])) { $params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params); @@ -242,7 +248,12 @@ class Url */ public function getAbsoluteUrl($separator = '&') { - return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator); + $relativeUrl = $this->getRelativeUrl($separator); + if ($relativeUrl === '#') { + return $relativeUrl; + } + + return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $relativeUrl; } /** From 294f9022f2abf9f00050b3d6ae8a737c6b7de75d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 10:53:01 +0200 Subject: [PATCH 051/225] Use the new navigation to render the menu refs #5600 --- application/controllers/LayoutController.php | 10 +- .../layouts/scripts/parts/navigation.phtml | 9 +- application/views/scripts/layout/menu.phtml | 7 +- library/Icinga/Application/Web.php | 113 ++++++++++++++++++ 4 files changed, 119 insertions(+), 20 deletions(-) diff --git a/application/controllers/LayoutController.php b/application/controllers/LayoutController.php index 335bee8e0..c7e814119 100644 --- a/application/controllers/LayoutController.php +++ b/application/controllers/LayoutController.php @@ -3,11 +3,8 @@ namespace Icinga\Controllers; +use Icinga\Application\Icinga; use Icinga\Web\Controller\ActionController; -use Icinga\Web\Hook; -use Icinga\Web\Menu; -use Icinga\Web\MenuRenderer; -use Icinga\Web\Url; /** * Create complex layout parts @@ -21,9 +18,6 @@ class LayoutController extends ActionController { $this->setAutorefreshInterval(15); $this->_helper->layout()->disableLayout(); - - $url = Url::fromRequest(); - $menu = new MenuRenderer(Menu::load(), $url->getRelativeUrl()); - $this->view->menuRenderer = $menu->useCustomRenderer(); + $this->view->menuRenderer = Icinga::app()->getMenu()->getRenderer(); } } diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml index 92273e4b4..78d399810 100644 --- a/application/layouts/scripts/parts/navigation.phtml +++ b/application/layouts/scripts/parts/navigation.phtml @@ -1,8 +1,6 @@ auth()->isAuthenticated()) { @@ -27,10 +25,7 @@ if (! $this->auth()->isAuthenticated()) { 'layout/menu.phtml', 'default', array( - 'menuRenderer' => new MenuRenderer( - Menu::load(), - Url::fromRequest()->without('renderLayout')->getRelativeUrl() - ) + 'menuRenderer' => Icinga::app()->getMenu()->getRenderer() ) ) ?> diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml index ee2ab0081..16520c4a3 100644 --- a/application/views/scripts/layout/menu.phtml +++ b/application/views/scripts/layout/menu.phtml @@ -12,8 +12,5 @@ if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?> autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> - - + +setHeading(t('Navigation')); ?> \ No newline at end of file diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index e854b8e51..c01e4923e 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -16,6 +16,7 @@ use Icinga\User; use Icinga\Util\TimezoneDetect; use Icinga\Util\Translator; use Icinga\Web\Controller\Dispatcher; +use Icinga\Web\Navigation\Navigation; use Icinga\Web\Notification; use Icinga\Web\Session; use Icinga\Web\Session\Session as BaseSession; @@ -139,6 +140,118 @@ class Web extends EmbeddedWeb return $this->viewRenderer; } + /** + * Return the app's menu + * + * @return Navigation + */ + public function getMenu() + { + if ($this->user !== null) { + $menu = array( + 'dashboard' => array( + 'label' => t('Dashboard'), + 'url' => 'dashboard', + 'icon' => 'dashboard', + 'priority' => 10 + ), + 'system' => array( + 'label' => t('System'), + 'icon' => 'services', + 'priority' => 700, + 'renderer' => array( + 'SummaryNavigationItemRenderer', + 'state' => 'critical' + ), + 'children' => array( + 'about' => array( + 'label' => t('About'), + 'url' => 'about', + 'priority' => 701 + ) + ) + ), + 'configuration' => array( + 'label' => t('Configuration'), + 'icon' => 'wrench', + 'permission' => 'config/*', + 'priority' => 800, + 'children' => array( + 'application' => array( + 'label' => t('Application'), + 'url' => 'config/general', + 'permission' => 'config/application/*', + 'priority' => 810 + ), + 'authentication' => array( + 'label' => t('Authentication'), + 'url' => 'config/userbackend', + 'permission' => 'config/authentication/*', + 'priority' => 820 + ), + 'roles' => array( + 'label' => t('Roles'), + 'url' => 'role/list', + 'permission' => 'config/authentication/roles/show', + 'priority' => 830 + ), + 'users' => array( + 'label' => t('Users'), + 'url' => 'user/list', + 'permission' => 'config/authentication/users/show', + 'priority' => 840 + ), + 'groups' => array( + 'label' => t('Usergroups'), + 'url' => 'group/list', + 'permission' => 'config/authentication/groups/show', + 'priority' => 850 + ), + 'modules' => array( + 'label' => t('Modules'), + 'url' => 'config/modules', + 'permission' => 'config/modules', + 'priority' => 890 + ) + ) + ), + 'user' => array( + 'label' => $this->user->getUsername(), + 'icon' => 'user', + 'priority' => 900, + 'children' => array( + 'preferences' => array( + 'label' => t('Preferences'), + 'url' => 'preference', + 'priority' => 910 + ), + 'logout' => array( + 'label' => t('Logout'), + 'url' => 'authentication/logout', + 'priority' => 990, + 'renderer' => array( + 'NavigationItemRenderer', + 'target' => '_self' + ) + ) + ) + ) + ); + + if (Logger::writesToFile()) { + $menu['system']['children']['application_log'] = array( + 'label' => t('Application Log'), + 'url' => 'list/applicationlog', + 'priority' => 710 + ); + } + } else { + $menu = array(); + } + + return Navigation::fromArray($menu); + } + /** * Dispatch public interface */ From 398f3ef413f03bade9957fd5b1fcfb414b73f2d2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:46:23 +0200 Subject: [PATCH 052/225] monitoring: Move custom navigation renderers to the correct namespace refs #5600 --- .../Renderer/BackendAvailabilityNavigationItemRenderer.php} | 0 .../Renderer/MonitoringBadgeNavigationItemRenderer.php} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename modules/monitoring/library/Monitoring/Web/{Menu/BackendAvailabilityMenuItemRenderer.php => Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php} (100%) rename modules/monitoring/library/Monitoring/Web/{Menu/MonitoringBadgeMenuItemRenderer.php => Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php} (100%) diff --git a/modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php similarity index 100% rename from modules/monitoring/library/Monitoring/Web/Menu/BackendAvailabilityMenuItemRenderer.php rename to modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php diff --git a/modules/monitoring/library/Monitoring/Web/Menu/MonitoringBadgeMenuItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php similarity index 100% rename from modules/monitoring/library/Monitoring/Web/Menu/MonitoringBadgeMenuItemRenderer.php rename to modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php From 1a42c04c8e6ae41197ab4103e277665ac3049fc2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:47:17 +0200 Subject: [PATCH 053/225] NavigationItem: Fix locating a module's custom navigation renderer refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 9b7e5e8fa..213eead3c 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -614,7 +614,7 @@ class NavigationItem implements IteratorAggregate $renderer = null; foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { - $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::RENDERER_NS . '\\' . $name; + $classPath = 'Icinga\\Module\\' . ucfirst($module->getName()) . '\\' . static::RENDERER_NS . '\\' . $name; if (class_exists($classPath)) { $renderer = new $classPath($options); break; From 044fe031da3d4577339f730633e92d7123fad84a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:47:38 +0200 Subject: [PATCH 054/225] NavigationItemRenderer: Add method init() refs #5600 --- .../Web/Navigation/Renderer/NavigationItemRenderer.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 00a42d407..70c89c5b7 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -45,6 +45,16 @@ class NavigationItemRenderer if (! empty($options)) { $this->setOptions($options); } + + $this->init(); + } + + /** + * Initialize this renderer + */ + public function init() + { + } /** From 8e24ec978dab2ed01f8fbf86677c5d7ae6e9cba1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:47:57 +0200 Subject: [PATCH 055/225] BadgeNavigationItemRenderer: Use view() instead of getView() refs #5600 --- .../Web/Navigation/Renderer/BadgeNavigationItemRenderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php index a5e09f555..fa0d82324 100644 --- a/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php @@ -107,8 +107,8 @@ abstract class BadgeNavigationItemRenderer extends NavigationItemRenderer if (($count = $this->getCount()) > 0) { return sprintf( '
        %s
        ', - $this->getView()->escape($this->getTitle()), - $this->getView()->escape($this->getState()), + $this->view()->escape($this->getTitle()), + $this->view()->escape($this->getState()), $count ); } From 881880f18f5961c506f8406cd949db0381b5a43d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:48:29 +0200 Subject: [PATCH 056/225] SummaryNavigationItemRenderer: Fix method getTitle() refs #5600 --- .../Web/Navigation/Renderer/SummaryNavigationItemRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php index 62cd94eb0..a1af94f59 100644 --- a/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php @@ -41,6 +41,6 @@ class SummaryNavigationItemRenderer extends BadgeNavigationItemRenderer */ public function getTitle() { - return join(', ', $this->titles); + return !empty($this->titles) ? join(', ', $this->titles) : ''; } } From a96bead01be724e646d9f073501e6925aa22ac6c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:49:54 +0200 Subject: [PATCH 057/225] monitoring: Adjust custom navigation renderer for backend availability checks refs #5600 --- modules/monitoring/configuration.php | 2 +- ...kendAvailabilityNavigationItemRenderer.php | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 12b8236ab..83eec6320 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -221,7 +221,7 @@ $section = $this->menuSection($this->translate('System')); $section->add($this->translate('Monitoring Health'), array( 'url' => 'monitoring/process/info', 'priority' => 720, - 'renderer' => 'Icinga\Module\Monitoring\Web\Menu\BackendAvailabilityMenuItemRenderer' + 'renderer' => 'BackendAvailabilityNavigationItemRenderer' )); /* diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php index ca589d3cb..5381f687f 100644 --- a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/BackendAvailabilityNavigationItemRenderer.php @@ -1,13 +1,13 @@ isCurrentlyRunning()) { - return 1; + try { + if ($this->isCurrentlyRunning()) { + return 0; + } + } catch (Exception $_) { + // pass } - return 0; + + return 1; } /** From 7a5dceac7bc950ad7e814880a742a87971326c04 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:50:53 +0200 Subject: [PATCH 058/225] monitoring: Adjust custom navigation renderer for DataView badges refs #5600 --- modules/monitoring/configuration.php | 6 +- .../MonitoringBadgeNavigationItemRenderer.php | 146 +++++++++--------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 83eec6320..7beabde05 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -90,7 +90,7 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv */ $section = $this->menuSection($this->translate('Problems'), array( 'renderer' => array( - 'SummaryMenuItemRenderer', + 'SummaryNavigationItemRenderer', 'state' => 'critical' ), 'icon' => 'block', @@ -98,7 +98,7 @@ $section = $this->menuSection($this->translate('Problems'), array( )); $section->add($this->translate('Unhandled Hosts'), array( 'renderer' => array( - 'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer', + 'MonitoringBadgeNavigationItemRenderer', 'columns' => array( 'hosts_down_unhandled' => $this->translate('%d unhandled hosts down') ), @@ -110,7 +110,7 @@ $section->add($this->translate('Unhandled Hosts'), array( )); $section->add($this->translate('Unhandled Services'), array( 'renderer' => array( - 'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer', + 'MonitoringBadgeNavigationItemRenderer', 'columns' => array( 'services_critical_unhandled' => $this->translate('%d unhandled services critical') ), diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php index 90d02fe52..14c44956e 100644 --- a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php @@ -1,23 +1,26 @@ dataView = $dataView; + return $this; + } - $this->columns = $configuration->get('columns'); - $this->state = $configuration->get('state'); - $this->dataView = $configuration->get('dataView'); + /** + * Return the dataview referred to by the navigation item + * + * @return string + */ + public function getDataView() + { + return $this->dataView; + } + /** + * Set the columns and titles displayed in the badge + * + * @param array $columns + * + * @return $this + */ + public function setColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Return the columns and titles displayed in the badge + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * {@inheritdoc} + */ + public function init() + { // clear the outdated summary cache, since new columns are being added. Optimally all menu item are constructed // before any rendering is going on to avoid trashing too man old requests if (isset(self::$summaries[$this->dataView])) { @@ -89,11 +112,11 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer if (! isset(self::$dataViews[$this->dataView])) { self::$dataViews[$this->dataView] = array(); } + foreach ($this->columns as $column => $title) { if (! array_search($column, self::$dataViews[$this->dataView])) { self::$dataViews[$this->dataView][] = $column; } - $this->titles[$column] = $title; } } @@ -116,12 +139,11 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer } /** - * Fetch the response from the database or access cache + * Fetch the dataview from the database or access cache * - * @param $view + * @param string $view * - * @return null - * @throws \Icinga\Exception\ConfigurationError + * @return object */ protected static function summary($view) { @@ -133,51 +155,29 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer static::applyRestriction('monitoring/filter/objects', $summary); self::$summaries[$view] = $summary->fetchRow(); } - return isset(self::$summaries[$view]) ? self::$summaries[$view] : null; + + return self::$summaries[$view]; } /** - * Defines the color of the badge - * - * @return string - */ - public function getState() - { - return $this->state; - } - - /** - * The amount of items to display in the badge - * - * @return int + * {@inheritdoc} */ public function getCount() { - $sum = self::summary($this->dataView); - $count = 0; + try { + $summary = self::summary($this->getDataView()); + } catch (Exception $_) { + return 0; + } - foreach ($this->columns as $col => $title) { - if (isset($sum->$col)) { - $count += $sum->$col; + $count = 0; + foreach ($this->getColumns() as $column => $title) { + if (isset($summary->$column) && $summary->$column > 0) { + $this->titles[] = sprintf($title, $summary->$column); + $count += $summary->$column; } } + return $count; } - - /** - * The tooltip title - * - * @return string - */ - public function getTitle() - { - $titles = array(); - $sum = $this->summary($this->dataView); - foreach ($this->columns as $column => $value) { - if (isset($sum->$column) && $sum->$column > 0) { - $titles[] = sprintf($this->titles[$column], $sum->$column); - } - } - return implode(', ', $titles); - } } From 8643fec309089ba1f9ac5e22a0ad3e7b04fd1128 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:54:23 +0200 Subject: [PATCH 059/225] Add class MenuItemContainer resf #5600 --- .../Application/Modules/MenuItemContainer.php | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 library/Icinga/Application/Modules/MenuItemContainer.php diff --git a/library/Icinga/Application/Modules/MenuItemContainer.php b/library/Icinga/Application/Modules/MenuItemContainer.php new file mode 100644 index 000000000..6f9ec7f93 --- /dev/null +++ b/library/Icinga/Application/Modules/MenuItemContainer.php @@ -0,0 +1,161 @@ +name = $name; + $this->children = array(); + $this->properties = $properties; + } + + /** + * Set this menu item's name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Return this menu item's name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set this menu item's properties + * + * @param array $properties + * + * @return $this + */ + public function setProperties(array $properties) + { + $this->properties = $properties; + return $this; + } + + /** + * Return this menu item's properties + * + * @return array + */ + public function getProperties() + { + return $this->properties ?: array(); + } + + /** + * Set this menu item's children + * + * @param MenuItemContainer[] $children + * + * @return $this + */ + public function setChildren(array $children) + { + $this->children = $children; + return $this; + } + + /** + * Return this menu item's children + * + * @return array + */ + public function getChildren() + { + return $this->children; + } + + /** + * Add a new child + * + * @param string $name + * @param array $properties + * + * @return MenuItemContainer The newly added menu item + */ + public function add($name, array $properties = null) + { + $child = new static($name, $properties); + $this->children[] = $child; + return $child; + } + + /** + * Allow dynamic setters and getters for properties + * + * @param string $name + * @param array $arguments + * + * @return mixed + * + * @throws ProgrammingError In case the called method is not supported + */ + public function __call($name, $arguments) + { + if (method_exists($this, $name)) { + return call_user_method_array($name, $this, $arguments); + } + + $type = substr($name, 0, 3); + if ($type !== 'set' && $type !== 'get') { + throw new ProgrammingError( + 'Dynamic method %s is not supported. Only getters (get*) and setters (set*) are.', + $name + ); + } + + $propertyName = strtolower(join('_', preg_split('~(?=[A-Z])~', lcfirst(substr($name, 3))))); + if ($type === 'set') { + $this->properties[$propertyName] = $arguments[0]; + return $this; + } else { // $type === 'get' + return array_key_exists($propertyName, $this->properties) ? $this->properties[$propertyName] : null; + } + } +} From 27a6b5bb759361cc5a5304fffcb1991e510fd2b2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:54:53 +0200 Subject: [PATCH 060/225] Module: Utilize MenuItemContainer instead of Menu refs #5600 --- library/Icinga/Application/Modules/Module.php | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 61256c7d5..89c36e974 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -11,7 +11,7 @@ use Icinga\Application\ApplicationBootstrap; use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Application\Logger; -use Icinga\Data\ConfigObject; +use Icinga\Application\Modules\MenuItemContainer; use Icinga\Exception\IcingaException; use Icinga\Exception\ProgrammingError; use Icinga\Module\Setup\SetupWizard; @@ -19,7 +19,7 @@ use Icinga\Util\File; use Icinga\Util\Translator; use Icinga\Web\Controller\Dispatcher; use Icinga\Web\Hook; -use Icinga\Web\Menu; +use Icinga\Web\Navigation\Navigation; use Icinga\Web\Widget; use Icinga\Web\Widget\Dashboard\Pane; @@ -189,7 +189,7 @@ class Module /** * A set of menu elements * - * @var Menu[] + * @var MenuItemContainer[] */ protected $menuItems = array(); @@ -301,14 +301,34 @@ class Module } /** - * Get all menu items + * Return this module's menu * - * @return array + * @return Navigation */ - public function getMenuItems() + public function getMenu() { $this->launchConfigScript(); - return $this->menuItems; + return $this->createMenu($this->menuItems); + } + + /** + * Create and return a new navigation for the given menu items + * + * @param MenuItemContainer[] $items + * + * @return Navigation + */ + private function createMenu(array $items) + { + $navigation = new Navigation(); + foreach ($items as $item) { + /** @var MenuItemContainer $item */ + $navigationItem = $navigation->createItem($item->getName(), $item->getProperties()); + $navigationItem->setChildren($this->createMenu($item->getChildren())); + $navigation->addItem($navigationItem); + } + + return $navigation; } /** @@ -317,14 +337,14 @@ class Module * @param string $name * @param array $properties * - * @return Menu + * @return MenuItemContainer */ - protected function menuSection($name, array $properties = array()) + protected function menuSection($name, array $properties = null) { if (array_key_exists($name, $this->menuItems)) { $this->menuItems[$name]->setProperties($properties); } else { - $this->menuItems[$name] = new Menu($name, new ConfigObject($properties)); + $this->menuItems[$name] = new MenuItemContainer($name, $properties); } return $this->menuItems[$name]; From 63f8f34c154bff92e8531974c79b15f886a5a512 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:55:31 +0200 Subject: [PATCH 061/225] Navigation: Add method load() Supports currently only module menus. refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 781eb4e8d..9d22617fb 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -354,6 +354,30 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return $this; } + /** + * Extend this navigation set with all additional items of the given type + * + * This will fetch navigation items from the following sources: + * * User Shareables + * * User Preferences + * * Modules + * Any existing entry will be overwritten by one that is coming later in order. + * + * @param string $type + * + * @return $this + */ + public function load($type) + { + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + if ($type === 'menu-item') { + $this->merge($module->getMenu()); + } + } + + return $this; + } + /** * Create and return a new set of navigation items for the given configuration * From ab23b56973b3983965a45a68de59c81ca1a85108 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 13:55:49 +0200 Subject: [PATCH 062/225] Web: Load module menus refs #5600 --- library/Icinga/Application/Web.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index c01e4923e..6be440260 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -249,7 +249,7 @@ class Web extends EmbeddedWeb $menu = array(); } - return Navigation::fromArray($menu); + return Navigation::fromArray($menu)->load('menu-item'); } /** From 0c3c38a2efeae3d9775337084f3df81e898cd25c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 14:06:44 +0200 Subject: [PATCH 063/225] monitoring: Provide a explicit priority for the history menu section refs #5600 --- modules/monitoring/configuration.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 7beabde05..87d5a6644 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -190,7 +190,8 @@ $section->add($this->translate('Notifications'), array( * History Section */ $section = $this->menuSection($this->translate('History'), array( - 'icon' => 'rewind' + 'icon' => 'rewind', + 'priority' => 90 )); $section->add($this->translate('Event Grid'), array( 'url' => 'monitoring/list/eventgrid', From b2a0f1b9c2ce31ec7866443c5e21c2ee85cd0f7d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 14:17:16 +0200 Subject: [PATCH 064/225] Navigation: Peform a case-insensitive search when merging items refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 9d22617fb..1500f9b9b 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -318,6 +318,28 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return $a->getPriority() > $b->getPriority() ? 1 : -1; } + /** + * Try to find and return a item with the given or a similar name + * + * @param string $name + * + * @return NavigationItem + */ + protected function findItem($name) + { + $item = $this->getItem($name); + if ($item !== null) { + return $item; + } + + $loweredName = strtolower($name); + foreach ($this->getItems() as $item) { + if (strtolower($item->getName()) === $loweredName) { + return $item; + } + } + } + /** * Merge this navigation with the given one * @@ -331,7 +353,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate { foreach ($navigation as $item) { /** @var $item NavigationItem */ - if (($existingItem = $this->getItem($item->getName())) !== null) { + if (($existingItem = $this->findItem($item->getName())) !== null) { if ($existingItem->conflictsWith($item)) { $name = $item->getName(); do { From 2af06e94150b2f7aab4806009a774b70bfb8d884 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 14:20:21 +0200 Subject: [PATCH 065/225] NavigationItem: Re-introduce a default priority of 100 refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 213eead3c..0aa97dbc0 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -117,6 +117,7 @@ class NavigationItem implements IteratorAggregate public function __construct($name, array $properties = null) { $this->setName($name); + $this->priority = 100; $this->children = new Navigation(); if (! empty($properties)) { From 5c2619dcb594138f1a7a3daf12ce3b780d5b0509 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 15:04:29 +0200 Subject: [PATCH 066/225] Add class NavigationItemContainer refs #5600 --- .../Application/Modules/MenuItemContainer.php | 120 +----------------- library/Icinga/Application/Modules/Module.php | 2 +- .../Modules/NavigationItemContainer.php | 117 +++++++++++++++++ 3 files changed, 125 insertions(+), 114 deletions(-) create mode 100644 library/Icinga/Application/Modules/NavigationItemContainer.php diff --git a/library/Icinga/Application/Modules/MenuItemContainer.php b/library/Icinga/Application/Modules/MenuItemContainer.php index 6f9ec7f93..2d9f176f4 100644 --- a/library/Icinga/Application/Modules/MenuItemContainer.php +++ b/library/Icinga/Application/Modules/MenuItemContainer.php @@ -4,24 +4,10 @@ namespace Icinga\Application\Modules; /** - * Container for module menus + * Container for module menu items */ -class MenuItemContainer +class MenuItemContainer extends NavigationItemContainer { - /** - * This menu item's name - * - * @var string - */ - protected $name; - - /** - * This menu item's properties - * - * @var array - */ - protected $properties; - /** * This menu item's children * @@ -29,65 +15,6 @@ class MenuItemContainer */ protected $children; - /** - * Create a new MenuItemContainer - * - * @param string $name - * @param array $properties - */ - public function __construct($name, array $properties = null) - { - $this->name = $name; - $this->children = array(); - $this->properties = $properties; - } - - /** - * Set this menu item's name - * - * @param string $name - * - * @return $this - */ - public function setName($name) - { - $this->name = $name; - return $this; - } - - /** - * Return this menu item's name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * Set this menu item's properties - * - * @param array $properties - * - * @return $this - */ - public function setProperties(array $properties) - { - $this->properties = $properties; - return $this; - } - - /** - * Return this menu item's properties - * - * @return array - */ - public function getProperties() - { - return $this->properties ?: array(); - } - /** * Set this menu item's children * @@ -108,54 +35,21 @@ class MenuItemContainer */ public function getChildren() { - return $this->children; + return $this->children ?: array(); } /** - * Add a new child + * Add a new sub menu * * @param string $name * @param array $properties * - * @return MenuItemContainer The newly added menu item + * @return MenuItemContainer The newly added sub menu */ - public function add($name, array $properties = null) + public function add($name, array $properties = array()) { - $child = new static($name, $properties); + $child = new MenuItemContainer($name, $properties); $this->children[] = $child; return $child; } - - /** - * Allow dynamic setters and getters for properties - * - * @param string $name - * @param array $arguments - * - * @return mixed - * - * @throws ProgrammingError In case the called method is not supported - */ - public function __call($name, $arguments) - { - if (method_exists($this, $name)) { - return call_user_method_array($name, $this, $arguments); - } - - $type = substr($name, 0, 3); - if ($type !== 'set' && $type !== 'get') { - throw new ProgrammingError( - 'Dynamic method %s is not supported. Only getters (get*) and setters (set*) are.', - $name - ); - } - - $propertyName = strtolower(join('_', preg_split('~(?=[A-Z])~', lcfirst(substr($name, 3))))); - if ($type === 'set') { - $this->properties[$propertyName] = $arguments[0]; - return $this; - } else { // $type === 'get' - return array_key_exists($propertyName, $this->properties) ? $this->properties[$propertyName] : null; - } - } } diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 89c36e974..ef5d90a4f 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -339,7 +339,7 @@ class Module * * @return MenuItemContainer */ - protected function menuSection($name, array $properties = null) + protected function menuSection($name, array $properties = array()) { if (array_key_exists($name, $this->menuItems)) { $this->menuItems[$name]->setProperties($properties); diff --git a/library/Icinga/Application/Modules/NavigationItemContainer.php b/library/Icinga/Application/Modules/NavigationItemContainer.php new file mode 100644 index 000000000..b59d52f60 --- /dev/null +++ b/library/Icinga/Application/Modules/NavigationItemContainer.php @@ -0,0 +1,117 @@ +name = $name; + $this->properties = $properties; + } + + /** + * Set this menu item's name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Return this menu item's name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set this menu item's properties + * + * @param array $properties + * + * @return $this + */ + public function setProperties(array $properties) + { + $this->properties = $properties; + return $this; + } + + /** + * Return this menu item's properties + * + * @return array + */ + public function getProperties() + { + return $this->properties ?: array(); + } + + /** + * Allow dynamic setters and getters for properties + * + * @param string $name + * @param array $arguments + * + * @return mixed + * + * @throws ProgrammingError In case the called method is not supported + */ + public function __call($name, $arguments) + { + if (method_exists($this, $name)) { + return call_user_method_array($name, $this, $arguments); + } + + $type = substr($name, 0, 3); + if ($type !== 'set' && $type !== 'get') { + throw new ProgrammingError( + 'Dynamic method %s is not supported. Only getters (get*) and setters (set*) are.', + $name + ); + } + + $propertyName = strtolower(join('_', preg_split('~(?=[A-Z])~', lcfirst(substr($name, 3))))); + if ($type === 'set') { + $this->properties[$propertyName] = $arguments[0]; + return $this; + } else { // $type === 'get' + return array_key_exists($propertyName, $this->properties) ? $this->properties[$propertyName] : null; + } + } +} From 70a48643c188ae565573b221994421e7a6370d39 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 15:25:19 +0200 Subject: [PATCH 067/225] Add class DashboardContainer refs #5600 --- .../Modules/DashboardContainer.php | 54 +++++++++++++++++++ library/Icinga/Application/Modules/Module.php | 45 ++++++++++++---- 2 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 library/Icinga/Application/Modules/DashboardContainer.php diff --git a/library/Icinga/Application/Modules/DashboardContainer.php b/library/Icinga/Application/Modules/DashboardContainer.php new file mode 100644 index 000000000..dac3b3ba5 --- /dev/null +++ b/library/Icinga/Application/Modules/DashboardContainer.php @@ -0,0 +1,54 @@ +dashlets = $dashlets; + return $this; + } + + /** + * Return this dashboard's dashlets + * + * @return array + */ + public function getDashlets() + { + return $this->dashlets ?: array(); + } + + /** + * Add a new dashlet + * + * @param string $name + * @param string $url + * + * @return $this + */ + public function add($name, $url) + { + $this->dashlets[$name] = $url; + return $this; + } +} diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index ef5d90a4f..fc3c1fcc0 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -11,6 +11,7 @@ use Icinga\Application\ApplicationBootstrap; use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Application\Logger; +use Icinga\Application\Modules\DashboardContainer; use Icinga\Application\Modules\MenuItemContainer; use Icinga\Exception\IcingaException; use Icinga\Exception\ProgrammingError; @@ -21,7 +22,6 @@ use Icinga\Web\Controller\Dispatcher; use Icinga\Web\Hook; use Icinga\Web\Navigation\Navigation; use Icinga\Web\Widget; -use Icinga\Web\Widget\Dashboard\Pane; /** * Module handling @@ -277,26 +277,53 @@ class Module } /** - * Get all pane items + * Return this module's dashboard * - * @return array + * @return Navigation */ - public function getPaneItems() + public function getDashboard() { $this->launchConfigScript(); - return $this->paneItems; + return $this->createDashboard($this->paneItems); } /** - * Add a pane to dashboard + * Create and return a new navigation for the given dashboard panes * - * @param string $name + * @param DashboardContainer[] $panes * - * @return Pane + * @return Navigation + */ + public function createDashboard(array $panes) + { + $navigation = new Navigation(); + foreach ($panes as $pane) { + /** @var DashboardContainer $pane */ + foreach ($pane->getDashlets() as $dashletName => $dashletUrl) { + $navigation->addItem( + $dashletName, + array( + 'type' => 'dashlet', + 'dashboard' => $pane->getName(), + 'url' => $dashletUrl + ) + ); + } + } + + return $navigation; + } + + /** + * Add or get a dashboard pane + * + * @param string $name + * + * @return DashboardContainer */ protected function dashboard($name) { - $this->paneItems[$name] = new Pane($name); + $this->paneItems[$name] = new DashboardContainer($name); return $this->paneItems[$name]; } From 95a61e89a9ea60fe18d16e8a13df29bd3cb4e361 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 15:51:33 +0200 Subject: [PATCH 068/225] Module: Fix how dashlets are loaded refs #5600 --- library/Icinga/Application/Modules/Module.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index fc3c1fcc0..3f6bc754c 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -299,16 +299,13 @@ class Module $navigation = new Navigation(); foreach ($panes as $pane) { /** @var DashboardContainer $pane */ - foreach ($pane->getDashlets() as $dashletName => $dashletUrl) { - $navigation->addItem( - $dashletName, - array( - 'type' => 'dashlet', - 'dashboard' => $pane->getName(), - 'url' => $dashletUrl - ) - ); - } + $navigation->addItem( + $pane->getName(), + array( + 'type' => 'dashboard-pane', + 'dashlets' => $pane->getDashlets() + ) + ); } return $navigation; From 6967fffb1f62332adee50954093d827df3fdd5f9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 15:51:51 +0200 Subject: [PATCH 069/225] Navigation: Add support dashboard-panes and check module permissions refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 1500f9b9b..2cf4376fa 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -391,9 +391,14 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ public function load($type) { - foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { - if ($type === 'menu-item') { - $this->merge($module->getMenu()); + $moduleManager = Icinga::app()->getModuleManager(); + foreach ($moduleManager->getLoadedModules() as $module) { + if (Auth::getInstance()->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) { + if ($type === 'menu-item') { + $this->merge($module->getMenu()); + } elseif ($type === 'dashboard-pane') { + $this->merge($module->getDashboard()); + } } } From 6dd49761fffb3e6a173538418e54e56902dfd5ee Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 4 Sep 2015 16:21:09 +0200 Subject: [PATCH 070/225] Navigation: Add method getActiveItem() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 2cf4376fa..408576f3c 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -221,6 +221,23 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return isset($this->items[$name]) ? $this->items[$name] : $default; } + /** + * Return the currently active item or the first one if none is active + * + * @return NavigationItem + */ + public function getActiveItem() + { + $firstItem = reset($this->items); + foreach ($this->items as $item) { + if ($item->getActive()) { + return $item; + } + } + + return $firstItem->setActive(); + } + /** * Return this navigation's items * From a3539b081750cec60be338523187b009a2e9100d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 08:37:06 +0200 Subject: [PATCH 071/225] Navigation: Fix method getActiveItem() failing badly for empty navigations refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 408576f3c..4307a68c5 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -228,14 +228,14 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ public function getActiveItem() { - $firstItem = reset($this->items); foreach ($this->items as $item) { if ($item->getActive()) { return $item; } } - return $firstItem->setActive(); + $firstItem = reset($this->items); + return $firstItem ? $firstItem->setActive() : null; } /** From b5711f403161c202c7269384fdfcd7cfe67172b0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 09:05:50 +0200 Subject: [PATCH 072/225] Dashboard: Load panes by utilizing the new Navigation This is only a quick fix to avoid adjusting the complete dashboard implementation. refs #5600 --- library/Icinga/Web/Widget/Dashboard.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 758ec8267..60ac28e1d 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -9,6 +9,8 @@ use Icinga\Exception\ConfigurationError; use Icinga\Exception\NotReadableError; use Icinga\Exception\ProgrammingError; use Icinga\User; +use Icinga\Web\Navigation\DashboardPane; +use Icinga\Web\Navigation\Navigation; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Dashlet as DashboardDashlet; use Icinga\Web\Url; @@ -68,16 +70,21 @@ class Dashboard extends AbstractWidget */ public function load() { - $manager = Icinga::app()->getModuleManager(); - foreach ($manager->getLoadedModules() as $module) { - if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) { - $this->mergePanes($module->getPaneItems()); + $navigation = new Navigation(); + $navigation->load('dashboard-pane'); + + $panes = array(); + foreach ($navigation as $dashboardPane) { + /** @var DashboardPane $dashboardPane */ + $pane = new Pane($dashboardPane->getName()); + foreach ($dashboardPane->getDashlets() as $title => $url) { + $pane->addDashlet($title, $url); } + $panes[] = $pane; } - $this->loadUserDashboards(); - + $this->mergePanes($panes); return $this; } From e63e15e471a19941b9eae586ac0e04fca9ec3628 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 09:06:40 +0200 Subject: [PATCH 073/225] Add class DashboardPane refs #5600 --- .../Icinga/Web/Navigation/DashboardPane.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 library/Icinga/Web/Navigation/DashboardPane.php diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php new file mode 100644 index 000000000..d1bfe2696 --- /dev/null +++ b/library/Icinga/Web/Navigation/DashboardPane.php @@ -0,0 +1,50 @@ +dashlets = $dashlets; + return $this; + } + + /** + * Return this pane's dashlets + * + * @return array + */ + public function getDashlets() + { + return $this->dashlets ?: array(); + } + + /** + * {@inheritdoc} + */ + public function init() + { + $this->setUrl(Url::fromPath('dashboard', array('pane' => $this->getName()))); + } +} From ca72bd5455ac81f39c0b01a42e8764af53d02c67 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 09:48:32 +0200 Subject: [PATCH 074/225] NavigationRendererInterface: Use "div" as default outer element tag "nav" is less often necessary. refs #5600 --- application/views/scripts/layout/menu.phtml | 2 +- .../Web/Navigation/Renderer/NavigationRendererInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml index 16520c4a3..36b7cbbdb 100644 --- a/application/views/scripts/layout/menu.phtml +++ b/application/views/scripts/layout/menu.phtml @@ -13,4 +13,4 @@ if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?> /> -setHeading(t('Navigation')); ?> \ No newline at end of file +setHeading(t('Navigation'))->setElementTag('nav'); ?> \ No newline at end of file diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php index d9306e29e..4e8ce6414 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php @@ -60,7 +60,7 @@ interface NavigationRendererInterface * * @var string */ - const OUTER_ELEMENT_TAG = 'nav'; + const OUTER_ELEMENT_TAG = 'div'; /** * The heading's rank From f9b68b683b277ed772687a55854002e7f77d28a3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 09:49:05 +0200 Subject: [PATCH 075/225] NavigationItem: Consider unknown properties as element attributes refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 0aa97dbc0..98644c8ca 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -527,6 +527,8 @@ class NavigationItem implements IteratorAggregate /** * Set this item's properties * + * Unknown properties (no matching setter) are considered as element attributes. + * * @param array $properties * * @return $this @@ -537,6 +539,8 @@ class NavigationItem implements IteratorAggregate $setter = 'set' . ucfirst($name); if (method_exists($this, $setter)) { $this->$setter($value); + } else { + $this->setAttribute($name, $value); } } From 4983d46dd7e99a583a62c0b9266c5bccdd00747b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 09:53:53 +0200 Subject: [PATCH 076/225] actions.phtml: Use the new Navigation to load and render action urls refs #5600 --- .../scripts/show/components/actions.phtml | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index 329a06fb9..9e228887d 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -1,25 +1,34 @@ %s ', $this->translate('opens in new window')); -$links = $object->getActionUrls(); -foreach ($links as $i => $link) { - $links[$i] = sprintf('%s ' . $newTabInfo . '', $link, 'Action'); +foreach ($object->getActionUrls() as $i => $link) { + $navigation->addItem( + 'Action ' . ($i + 1) . $newTabInfo, + array( + 'url' => $link, + 'target' => '_blank' + ) + ); } if (isset($this->actions)) { foreach ($this->actions as $id => $action) { - $links[] = sprintf('%s', $action, $id); + $navigation->addItem($id, array('url' => $action)); } } -if (empty($links)) { +if ($navigation->isEmpty()) { return; } ?> - translate('Actions') ?> - ", $links) ?> - + translate('Actions'); ?> + getRenderer()->setElementTag('td'); ?> + \ No newline at end of file From 716e532d16db5d88fb5928636d48b21ac71ab17e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 10:34:21 +0200 Subject: [PATCH 077/225] notes.phtml: Use the new Navigation to load and render notes urls refs #5600 --- .../views/scripts/show/components/notes.phtml | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml index 4a5041e50..94f645897 100644 --- a/modules/monitoring/application/views/scripts/show/components/notes.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml @@ -1,26 +1,38 @@ getNotes()); -$links = $object->getNotesUrls(); -if (! empty($links) || ! empty($notes)): ?> +use Icinga\Web\Navigation\Navigation; + +$navigation = new Navigation(); + +$notes = trim($object->getNotes()); +if ($notes) { + $navigation->addItem($notes); +} + +$links = $object->getNotesUrls(); +if (! empty($links)) { + // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 + $newTabInfo = sprintf( + ' %s ', + $this->translate('opens in new window') + ); + + foreach ($links as $link) { + $navigation->addItem( + $this->escape($link) . $newTabInfo, + array( + 'url' => $link, + 'target' => '_blank' + ) + ); + } +} + +if ($navigation->isEmpty()) { + return; +} +?> - translate('Notes') ?> - - '; - } - // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 - $newTabInfo = sprintf( - ' %s ', - $this->translate('opens in new window') - ); - $linkText = '%s ' . $newTabInfo . ''; - foreach ($links as $i => $link) { - $links[$i] = sprintf($linkText, $this->escape($link), $this->escape($link)); - } - echo implode('
        ', $links); - ?> - - - + translate('Notes'); ?> + getRenderer()->setElementTag('td'); ?> + \ No newline at end of file From 860edb9768b50d6f20596b1cc58f4788a8fcee8b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 10:53:44 +0200 Subject: [PATCH 078/225] Add class Action refs #5600 --- .../Monitoring/Web/Navigation/Action.php | 65 +++++++++++++++++++ .../Monitoring/Web/Navigation/HostAction.php | 11 ++++ .../Monitoring/Web/Navigation/HostNote.php | 11 ++++ .../Web/Navigation/ServiceAction.php | 11 ++++ .../Monitoring/Web/Navigation/ServiceNote.php | 11 ++++ 5 files changed, 109 insertions(+) create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/Action.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php create mode 100644 modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php new file mode 100644 index 000000000..46eb51145 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php @@ -0,0 +1,65 @@ +object = $object; + return $this; + } + + /** + * Return this action's object + * + * @return MonitoredObject + */ + public function getObject() + { + return $this->object; + } + + /** + * {@inheritdoc} + */ + public function getUrl() + { + $url = parent::getUrl(); + if (! $this->resolved) { + $this->setUrl(Macro::resolveMacros($url->getAbsoluteUrl(), $this->getObject())); + $this->resolved = true; + } + + return $url; + } +} diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php new file mode 100644 index 000000000..9d391dfc7 --- /dev/null +++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php @@ -0,0 +1,11 @@ + Date: Mon, 7 Sep 2015 10:54:43 +0200 Subject: [PATCH 079/225] actions.phtml: Load actions from shareables and user preferences refs #5600 --- .../application/views/scripts/show/components/actions.phtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index 9e228887d..1f39f6996 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -3,6 +3,10 @@ use Icinga\Web\Navigation\Navigation; $navigation = new Navigation(); +$navigation->load($object->getType() . '-action'); +foreach ($navigation as $item) { + $item->setObject($object); +} // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201 $newTabInfo = sprintf(' %s ', $this->translate('opens in new window')); From a1b219f97d8bb0b324052ca68cd8280acc589a6c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 10:54:56 +0200 Subject: [PATCH 080/225] notes.phtml: Load notes from shareables and user preferences refs #5600 --- .../application/views/scripts/show/components/notes.phtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml index 94f645897..c5179dcfa 100644 --- a/modules/monitoring/application/views/scripts/show/components/notes.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml @@ -3,6 +3,10 @@ use Icinga\Web\Navigation\Navigation; $navigation = new Navigation(); +$navigation->load($object->getType() . '-note'); +foreach ($navigation as $item) { + $item->setObject($object); +} $notes = trim($object->getNotes()); if ($notes) { From 1be6dc05535901017025faf227c2ae75e5ad716e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 11:06:11 +0200 Subject: [PATCH 081/225] Module.php: Allow modules to provide configurable navigation items refs #5600 --- library/Icinga/Application/Modules/Module.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 3f6bc754c..911fd058c 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -221,6 +221,13 @@ class Module */ protected $userGroupBackends = array(); + /** + * This module's configurable navigation items + * + * @var array + */ + protected $navigationItems = array(); + /** * Create a new module object * @@ -875,6 +882,17 @@ class Module return $this->userGroupBackends; } + /** + * Return this module's configurable navigation items + * + * @return array + */ + public function getNavigationItems() + { + $this->launchConfigScript(); + return $this->navigationItems; + } + /** * Provide a named permission * @@ -979,6 +997,19 @@ class Module return $this; } + /** + * Provide a new type of configurable navigation item + * + * @param string $type + * + * @return $this + */ + protected function provideNavigationItem($type) + { + $this->navigationItems[] = $type; + return $this; + } + /** * Register module namespaces on our class loader * From 4487832d5f06e79029624bcc67da6672bf4ef040 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 11:07:22 +0200 Subject: [PATCH 082/225] monitoring: Provide configurable navigation items refs #5600 --- modules/monitoring/configuration.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 87d5a6644..a6c454027 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -85,6 +85,14 @@ $this->provideSearchUrl($this->translate('Services'), 'monitoring/list/services? $this->provideSearchUrl($this->translate('Hostgroups'), 'monitoring/list/hostgroups?limit=10', 97); $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/servicegroups?limit=10', 96); +/* + * Available navigation items + */ +$this->provideNavigationItem('host-action'); +$this->provideNavigationItem('service-action'); +$this->provideNavigationItem('host-note'); +$this->provideNavigationItem('service-note'); + /* * Problems Section */ From 274a0c8a096b3268802861c0f0901d42129c1737 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 11:20:36 +0200 Subject: [PATCH 083/225] Store a user's preferences in a dedicated directory rather than in a single file refs #5600 --- doc/installation.md | 5 +++++ library/Icinga/User/Preferences/Store/IniStore.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/installation.md b/doc/installation.md index f47d9b0b7..f0fbcadeb 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -302,3 +302,8 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar * The **instances.ini** configuration file provided by the monitoring module has been renamed to **commandtransports.ini**. The content and location of the file remains unchanged. + +* The location of a user's preferences has been changed from + **/preferences/.ini** to + **/preferences//config.ini**. + The content of the file remains unchanged. \ No newline at end of file diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php index 88fa7edec..83deb6f1b 100644 --- a/library/Icinga/User/Preferences/Store/IniStore.php +++ b/library/Icinga/User/Preferences/Store/IniStore.php @@ -34,7 +34,7 @@ class IniStore extends PreferencesStore protected function init() { $this->preferencesFile = sprintf( - '%s/%s.ini', + '%s/%s/config.ini', $this->getStoreConfig()->location, strtolower($this->getUser()->getUsername()) ); From 7d167d0191c158174d42070907b7e62cd38936ea Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 12:00:35 +0200 Subject: [PATCH 084/225] Action: Do not fail if no url is set refs #5600 --- modules/monitoring/library/Monitoring/Web/Navigation/Action.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php index 46eb51145..59de17c29 100644 --- a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php @@ -55,7 +55,7 @@ class Action extends NavigationItem public function getUrl() { $url = parent::getUrl(); - if (! $this->resolved) { + if (! $this->resolved && $url !== null) { $this->setUrl(Macro::resolveMacros($url->getAbsoluteUrl(), $this->getObject())); $this->resolved = true; } From 411c6e0546f00bbda7189c21f4c7a1c3a0675960 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 12:01:05 +0200 Subject: [PATCH 085/225] Navigation: Fix that a module's item class is not found refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 4307a68c5..8b0990e2e 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -141,7 +141,12 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate $item = null; foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { - $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::NAVIGATION_NS . '\\' . $itemType; + $classPath = 'Icinga\\Module\\' + . ucfirst($module->getName()) + . '\\' + . static::NAVIGATION_NS + . '\\' + . $itemType; if (class_exists($classPath)) { $item = new $classPath($name, $properties); break; From a012595e3d96a7a3e35146c21c93a8333cebe153 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 12:19:54 +0200 Subject: [PATCH 086/225] User: Add method getNavigation() refs #5600 --- library/Icinga/User.php | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/library/Icinga/User.php b/library/Icinga/User.php index 11d8177d1..1b2dc9398 100644 --- a/library/Icinga/User.php +++ b/library/Icinga/User.php @@ -5,7 +5,9 @@ namespace Icinga; use DateTimeZone; use InvalidArgumentException; +use Icinga\Application\Config; use Icinga\User\Preferences; +use Icinga\Web\Navigation\Navigation; /** * This class represents an authorized user @@ -476,4 +478,48 @@ class User return false; } + + /** + * Load and return this user's configured navigation of the given type + * + * @param string $type + * + * @return Navigation + */ + public function getNavigation($type) + { + $config = Config::fromIni( + Config::resolvePath('preferences') + . DIRECTORY_SEPARATOR + . $this->getUsername() + . DIRECTORY_SEPARATOR + . 'navigation.ini' + )->getConfigObject(); + $config->setKeyColumn('name'); + + $navigation = new Navigation(); + if ($type === 'dashboard-pane') { + $panes = array(); + foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) { + // TODO: Throw ConfigurationError if pane or url is missing + $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; + } + + foreach ($panes as $paneName => $dashlets) { + $navigation->addItem( + $paneName, + array( + 'type' => 'dashboard-pane', + 'dashlets' => $dashlets + ) + ); + } + } else { + foreach ($config->select()->where('type', $type) as $name => $typeConfig) { + $navigation->addItem($name, $typeConfig->toArray()); + } + } + + return $navigation; + } } From f958a1c3236485cde5b726c393a3168f56981f15 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 12:20:14 +0200 Subject: [PATCH 087/225] Navigation: Load a user's navigation items refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 8b0990e2e..77128e308 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -413,9 +413,12 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ public function load($type) { + $user = Auth::getInstance()->getUser(); + $this->merge($user->getNavigation($type)); + $moduleManager = Icinga::app()->getModuleManager(); foreach ($moduleManager->getLoadedModules() as $module) { - if (Auth::getInstance()->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) { + if ($user->can($moduleManager::MODULE_PERMISSION_NS . $module->getName())) { if ($type === 'menu-item') { $this->merge($module->getMenu()); } elseif ($type === 'dashboard-pane') { From 451a0c91f2535f09d55ed16761dff510b573dc5e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:19:36 +0200 Subject: [PATCH 088/225] NavigationItem: Add missing return statement in method merge() refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 98644c8ca..bb20db9e0 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -583,6 +583,8 @@ class NavigationItem implements IteratorAggregate if ($item->hasChildren()) { $this->getChildren()->merge($item->getChildren()); } + + return $this; } /** From 0feaec7af136b1f42ed57587e50f47d86018db2a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:20:17 +0200 Subject: [PATCH 089/225] NavigationItem: Return a instance of Url in method getUrl(), really refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index bb20db9e0..4a99fa295 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -446,7 +446,7 @@ class NavigationItem implements IteratorAggregate public function getUrl() { if ($this->url === null && $this->hasChildren()) { - return '#'; + $this->setUrl(Url::fromPath('#')); } return $this->url; From bf2cb9ab7e7a68132084a693d90f31f7fcc09cff Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:20:49 +0200 Subject: [PATCH 090/225] NavigationItem: Provide a more sophisticated conflict detection refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 4a99fa295..c56dab7e4 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -596,11 +596,15 @@ class NavigationItem implements IteratorAggregate */ public function conflictsWith(NavigationItem $item) { + if (! $item instanceof $this) { + return false; + } + if ($this->getUrl() === null || $item->getUrl() === null) { return false; } - return $this->getUrl() !== $item->getUrl(); + return !$this->getUrl()->matches($item->getUrl()); } /** From ee43fdad0a2cb337071a451b39179660db5da678 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:21:31 +0200 Subject: [PATCH 091/225] DashboardPane: Consider dashlets when merging refs #5600 --- library/Icinga/Web/Navigation/DashboardPane.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php index d1bfe2696..200287668 100644 --- a/library/Icinga/Web/Navigation/DashboardPane.php +++ b/library/Icinga/Web/Navigation/DashboardPane.php @@ -47,4 +47,17 @@ class DashboardPane extends NavigationItem { $this->setUrl(Url::fromPath('dashboard', array('pane' => $this->getName()))); } + + /** + * {@inheritdoc} + */ + public function merge(NavigationItem $item) + { + parent::merge($item); + + $this->setDashlets(array_merge( + $this->getDashlets(), + $item->getDashlets() + )); + } } From 27f3a8f152fb092aa09622f2b630e7eb1fdceae3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:22:02 +0200 Subject: [PATCH 092/225] Web: Add method getSharedNavigation() refs #5600 --- library/Icinga/Application/Web.php | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 6be440260..c57569ce3 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -140,6 +140,78 @@ class Web extends EmbeddedWeb return $this->viewRenderer; } + private function hasAccessToSharedNavigationItem(& $config) + { + // TODO: Provide a more sophisticated solution + + if (isset($config['owner']) && $config['owner'] === $this->user->getUsername()) { + unset($config['owner']); + return true; + } + + if (isset($config['users'])) { + $users = array_map('trim', explode(',', strtolower($config['users']))); + if (in_array($this->user->getUsername(), $users, true)) { + unset($config['users']); + return true; + } + } + + if (isset($config['groups'])) { + $groups = array_map('trim', explode(',', strtolower($config['groups']))); + $userGroups = array_map('strtolower', $this->user->getGroups()); + $matches = array_intersect($userGroups, $groups); + if (! empty($matches)) { + unset($config['groups']); + return true; + } + } + + return false; + } + + /** + * Load and return the shared navigation of the given type + * + * @param string $type + * + * @return Navigation + */ + public function getSharedNavigation($type) + { + $config = Config::app('navigation')->getConfigObject(); + $config->setKeyColumn('name'); + + $navigation = new Navigation(); + if ($type === 'dashboard-pane') { + $panes = array(); + foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) { + if ($this->hasAccessToSharedNavigationItem($dashletConfig)) { + // TODO: Throw ConfigurationError if pane or url is missing + $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; + } + } + + foreach ($panes as $paneName => $dashlets) { + $navigation->addItem( + $paneName, + array( + 'type' => 'dashboard-pane', + 'dashlets' => $dashlets + ) + ); + } + } else { + foreach ($config->select()->where('type', $type) as $name => $typeConfig) { + if ($this->hasAccessToSharedNavigationItem($typeConfig)) { + $navigation->addItem($name, $typeConfig->toArray()); + } + } + } + + return $navigation; + } + /** * Return the app's menu * From f9441c91d5bb0f6e47ecdac6d7fec4277966f6eb Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:22:36 +0200 Subject: [PATCH 093/225] Navigation: Load shared navigation items refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 77128e308..8f1969a7f 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -413,9 +413,14 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate */ public function load($type) { + // Shareables + $this->merge(Icinga::app()->getSharedNavigation($type)); + + // User Preferences $user = Auth::getInstance()->getUser(); $this->merge($user->getNavigation($type)); + // Modules $moduleManager = Icinga::app()->getModuleManager(); foreach ($moduleManager->getLoadedModules() as $module) { if ($user->can($moduleManager::MODULE_PERMISSION_NS . $module->getName())) { From 6b13f2a9870a926cf319dfc362ccf7947c888042 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:25:25 +0200 Subject: [PATCH 094/225] NavigationRenderer: Do not render an empty header refs #5600 --- .../Web/Navigation/Renderer/NavigationRenderer.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index bfba7d117..219df6e2c 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -244,11 +244,13 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa $this->getElementTag(), $this->getCssClass() !== null ? ' class="' . $this->getCssClass() . '"' : '' ); - $content[] = sprintf( - '%2$s', - static::HEADING_RANK, - $this->view()->escape($this->getHeading()) - ); + if (($heading = $this->getHeading()) !== null) { + $content[] = sprintf( + '%2$s', + static::HEADING_RANK, + $this->view()->escape($heading) + ); + } $content[] = $this->beginChildrenMarkup(); return join("\n", $content); } From 21180e2fd403bbf7a8b43b67b0a906fc17945f42 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:26:44 +0200 Subject: [PATCH 095/225] NavigationItem: Fix method conflictsWith() ... refs #5600 --- library/Icinga/Web/Navigation/NavigationItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index c56dab7e4..01888c41d 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -597,7 +597,7 @@ class NavigationItem implements IteratorAggregate public function conflictsWith(NavigationItem $item) { if (! $item instanceof $this) { - return false; + return true; } if ($this->getUrl() === null || $item->getUrl() === null) { From b1e3519353d85e3189546e126c3f36f2af1c6722 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 13:55:19 +0200 Subject: [PATCH 096/225] Add class NavigationController (WIP) refs #5600 --- .../controllers/NavigationController.php | 52 +++++++++++++++++++ library/Icinga/Application/Web.php | 19 +++++-- 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 application/controllers/NavigationController.php diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php new file mode 100644 index 000000000..f3ba1eaa9 --- /dev/null +++ b/application/controllers/NavigationController.php @@ -0,0 +1,52 @@ +assertPermission('config/application/navigation'); + } + + /** + * Add a navigation item + */ + public function addAction() + { + + } + + /** + * Edit a navigation item + */ + public function editAction() + { + + } + + /** + * Remove a navigation item + */ + public function removeAction() + { + + } +} diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index c57569ce3..06fe56a17 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -255,29 +255,35 @@ class Web extends EmbeddedWeb 'permission' => 'config/application/*', 'priority' => 810 ), + 'navigation' => array( + 'label' => t('Shared Navigation'), + 'url' => 'navigation/shared', + 'permission' => 'config/application/navigation', + 'priority' => 820, + ), 'authentication' => array( 'label' => t('Authentication'), 'url' => 'config/userbackend', 'permission' => 'config/authentication/*', - 'priority' => 820 + 'priority' => 830 ), 'roles' => array( 'label' => t('Roles'), 'url' => 'role/list', 'permission' => 'config/authentication/roles/show', - 'priority' => 830 + 'priority' => 840 ), 'users' => array( 'label' => t('Users'), 'url' => 'user/list', 'permission' => 'config/authentication/users/show', - 'priority' => 840 + 'priority' => 850 ), 'groups' => array( 'label' => t('Usergroups'), 'url' => 'group/list', 'permission' => 'config/authentication/groups/show', - 'priority' => 850 + 'priority' => 860 ), 'modules' => array( 'label' => t('Modules'), @@ -297,6 +303,11 @@ class Web extends EmbeddedWeb 'url' => 'preference', 'priority' => 910 ), + 'navigation' => array( + 'label' => t('Navigation'), + 'url' => 'navigation', + 'priority' => 920 + ), 'logout' => array( 'label' => t('Logout'), 'url' => 'authentication/logout', From 6a00eaf34d57ac19ab315e6a2abcfc062e8a6f79 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 14:01:28 +0200 Subject: [PATCH 097/225] User: Add method loadNavigationConfig() refs #5600 --- library/Icinga/User.php | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/library/Icinga/User.php b/library/Icinga/User.php index 1b2dc9398..4af29b35f 100644 --- a/library/Icinga/User.php +++ b/library/Icinga/User.php @@ -479,6 +479,22 @@ class User return false; } + /** + * Load and return this user's navigation configuration + * + * @return Config + */ + public function loadNavigationConfig() + { + return Config::fromIni( + Config::resolvePath('preferences') + . DIRECTORY_SEPARATOR + . $this->getUsername() + . DIRECTORY_SEPARATOR + . 'navigation.ini' + ); + } + /** * Load and return this user's configured navigation of the given type * @@ -488,14 +504,8 @@ class User */ public function getNavigation($type) { - $config = Config::fromIni( - Config::resolvePath('preferences') - . DIRECTORY_SEPARATOR - . $this->getUsername() - . DIRECTORY_SEPARATOR - . 'navigation.ini' - )->getConfigObject(); - $config->setKeyColumn('name'); + $config = $this->loadNavigationConfig(); + $config->getConfigObject()->setKeyColumn('name'); $navigation = new Navigation(); if ($type === 'dashboard-pane') { From 8e6fc1580ec47d7b392c4ed8e39698e351c2c501 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 15:01:55 +0200 Subject: [PATCH 098/225] NavigationController: Implement indexAction() refs #5600 --- .../controllers/NavigationController.php | 19 ++++++++- .../views/scripts/navigation/index.phtml | 39 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 application/views/scripts/navigation/index.phtml diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index f3ba1eaa9..74d93f49a 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -3,6 +3,7 @@ namespace Icinga\Controllers; +use Icinga\Application\Config; use Icinga\Web\Controller; /** @@ -15,7 +16,23 @@ class NavigationController extends Controller */ public function indexAction() { - + $user = $this->Auth()->getUser(); + $userConfig = $user->loadNavigationConfig(); + $sharedConfig = Config::app('navigation'); + + $this->view->items = array_merge( + $sharedConfig->select()->where('owner', $user->getUsername())->fetchAll(), + iterator_to_array($userConfig) + ); + + $this->getTabs()->add( + 'navigation', + array( + 'title' => $this->translate('List and configure your own navigation items'), + 'label' => $this->translate('Navigation'), + 'url' => 'navigation' + ) + )->activate('navigation'); } /** diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml new file mode 100644 index 000000000..6bf60eb3c --- /dev/null +++ b/application/views/scripts/navigation/index.phtml @@ -0,0 +1,39 @@ +compact): ?> +
        + tabs; ?> +
        + +
        + + icon('plus'); ?> translate('Create A New Navigation Item'); ?> + + + + + + + + $item): ?> + + + + + + +
        translate('Navigation'); ?>translate('Remove'); ?>
        qlink( + $name, + 'navigation/edit', + array('name' => $name), + array( + 'title' => sprintf($this->translate('Edit navigation item %s'), $name) + ) + ); ?>qlink( + '', + 'navigation/remove', + array('name' => $name), + array( + 'icon' => 'trash', + 'title' => sprintf($this->translate('Remove navigation item %s'), $name) + ) + ); ?>
        +
        \ No newline at end of file From 04a14a46b6dd280d620a03e615eb39914ed1226e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 15:04:31 +0200 Subject: [PATCH 099/225] NavigationController: Implement sharedAction() refs #5600 --- .../controllers/NavigationController.php | 33 +++++++++++++++++++ .../views/scripts/navigation/shared.phtml | 28 ++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 application/views/scripts/navigation/shared.phtml diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 74d93f49a..4d01b6e2e 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -5,6 +5,8 @@ namespace Icinga\Controllers; use Icinga\Application\Config; use Icinga\Web\Controller; +use Icinga\Web\Form; +use Icinga\Web\Url; /** * Navigation configuration @@ -41,6 +43,37 @@ class NavigationController extends Controller public function sharedAction() { $this->assertPermission('config/application/navigation'); + $this->view->items = Config::app('navigation'); + + $removeForm = new Form(); + $removeForm->setUidDisabled(); + $removeForm->setAction(Url::fromPath('navigation/unshare')); + $removeForm->addElement('hidden', 'name', array( + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('hidden', 'redirect', array( + 'value' => Url::fromPath('navigation/shared'), + 'decorators' => array('ViewHelper') + )); + $removeForm->addElement('button', 'btn_submit', array( + 'escape' => false, + 'type' => 'submit', + 'class' => 'link-like spinner', + 'value' => 'btn_submit', + 'decorators' => array('ViewHelper'), + 'label' => $this->view->icon('trash'), + 'title' => $this->translate('Unshare this navigation item') + )); + $this->view->removeForm = $removeForm; + + $this->getTabs()->add( + 'navigation/shared', + array( + 'title' => $this->translate('List and configure shared navigation items'), + 'label' => $this->translate('Shared Navigation'), + 'url' => 'navigation/shared' + ) + )->activate('navigation/shared'); } /** diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml new file mode 100644 index 000000000..657495ca3 --- /dev/null +++ b/application/views/scripts/navigation/shared.phtml @@ -0,0 +1,28 @@ +compact): ?> +
        + tabs; ?> +
        + +
        + + + + + + + $item): ?> + + + + + + +
        translate('Shared Navigation'); ?>translate('Remove'); ?>
        qlink( + $name, + 'navigation/edit', + array('name' => $name), + array( + 'title' => sprintf($this->translate('Edit shared navigation item %s'), $name) + ) + ); ?>setDefault('name', $name); ?>
        +
        \ No newline at end of file From 6f7059ef9afc6e407fbf07fd7d052ca725567161 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 15:21:21 +0200 Subject: [PATCH 100/225] NavigationController: Implement unshareAction() refs #5600 --- .../controllers/NavigationController.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 4d01b6e2e..79a191b34 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -4,6 +4,8 @@ namespace Icinga\Controllers; use Icinga\Application\Config; +use Icinga\Exception\NotFoundError; +use Icinga\Forms\Navigation\NavigationItemForm; use Icinga\Web\Controller; use Icinga\Web\Form; use Icinga\Web\Url; @@ -99,4 +101,55 @@ class NavigationController extends Controller { } + + /** + * Unshare a navigation item + */ + public function unshareAction() + { + $this->assertPermission('config/application/navigation'); + $this->assertHttpMethod('POST'); + + $navigationItemForm = new NavigationItemForm(); + $navigationItemForm->setIniConfig(Config::app('navigation')); + + $form = new Form(array( + 'onSuccess' => function ($form) use ($navigationItemForm) { + try { + if ($navigationItemForm->unshare($form->getValue('name'))) { + Notification::success(sprintf( + t('Navigation item "%s" has been unshared'), + $form->getValue('name') + )); + } else { + Notification::error(sprintf( + t('Failed to unshare navigation item "%s"'), + $form->getValue('name') + )); + } + } catch (NotFoundError $e) { + throw $e; + } catch (Exception $e) { + Notification::error($e->getMessage()); + } + + $redirect = $form->getValue('redirect'); + if (! empty($redirect)) { + $form->setRedirectUrl(htmlspecialchars_decode($redirect)); + } + + return true; + } + )); + $form->setUidDisabled(); + $form->setSubmitLabel('btn_submit'); // Required to ensure that isSubmitted() is called + $form->addElement('hidden', 'name', array('required' => true)); + $form->addElement('hidden', 'redirect'); + + try { + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $form->getValue('name'))); + } + } } From 55a961b41175e59d0583c86a30ea1d0b57879893 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 15:43:06 +0200 Subject: [PATCH 101/225] NavigationController: Implement editAction() refs #5600 --- .../controllers/NavigationController.php | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 79a191b34..7695e51f3 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -3,9 +3,10 @@ namespace Icinga\Controllers; +use Exception; use Icinga\Application\Config; use Icinga\Exception\NotFoundError; -use Icinga\Forms\Navigation\NavigationItemForm; +use Icinga\Forms\Navigation\NavigationConfigForm; use Icinga\Web\Controller; use Icinga\Web\Form; use Icinga\Web\Url; @@ -91,7 +92,50 @@ class NavigationController extends Controller */ public function editAction() { - + $itemName = $this->params->getRequired('name'); + $user = $this->Auth()->getUser(); + + $config = $user->loadNavigationConfig(); + if (! $config->hasSection($itemName) && $user->can('config/application/navigation')) { + $config = Config::app('navigation'); + } + + $form = new NavigationConfigForm(); + $form->setIniConfig($config); + $form->setRedirectUrl('navigation'); + $form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName)); + $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) { + try { + $form->edit($itemName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); + } catch (NotFoundError $e) { + throw $e; + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(sprintf(t('Navigation item "%s" successfully updated'), $itemName)); + return true; + } + + return false; + }); + + try { + $form->load($itemName); + $form->handleRequest(); + } catch (NotFoundError $_) { + $this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $itemName)); + } + + $this->view->form = $form; + $this->render('form'); } /** @@ -110,13 +154,13 @@ class NavigationController extends Controller $this->assertPermission('config/application/navigation'); $this->assertHttpMethod('POST'); - $navigationItemForm = new NavigationItemForm(); - $navigationItemForm->setIniConfig(Config::app('navigation')); + $navigationConfigForm = new NavigationConfigForm(); + $navigationConfigForm->setIniConfig(Config::app('navigation')); $form = new Form(array( - 'onSuccess' => function ($form) use ($navigationItemForm) { + 'onSuccess' => function ($form) use ($navigationConfigForm) { try { - if ($navigationItemForm->unshare($form->getValue('name'))) { + if ($navigationConfigForm->unshare($form->getValue('name'))) { Notification::success(sprintf( t('Navigation item "%s" has been unshared'), $form->getValue('name') From bd32f0940745c25f3181b0c105731043db99c484 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 15:50:26 +0200 Subject: [PATCH 102/225] NavigationController: Implement addAction() refs #5600 --- .../controllers/NavigationController.php | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 7695e51f3..1531d5563 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -84,7 +84,30 @@ class NavigationController extends Controller */ public function addAction() { - + $form = new NavigationConfigForm(); + $form->setRedirectUrl('navigation'); + $form->setTitle($this->translate('Create New Navigation Item')); + $form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.')); + $form->setIniConfig($this->Auth()->getUser()->loadNavigationConfig()); + $form->setOnSuccess(function (NavigationConfigForm $form) { + try { + $form->add(array_filter($form->getValues())); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($form->save()) { + Notification::success(t('Navigation item successfully created')); + return true; + } + + return false; + }); + $form->handleRequest(); + + $this->view->form = $form; + $this->render('form'); } /** From 3dc26e2dbe0432cc43349dc6a9cbd3cb2b0fb8da Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 16:02:12 +0200 Subject: [PATCH 103/225] NavigationController: Implement removeAction() refs #5600 --- .../controllers/NavigationController.php | 29 ++++++++++++++++++- .../views/scripts/navigation/form.phtml | 6 ++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 application/views/scripts/navigation/form.phtml diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 1531d5563..080ef328e 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -6,9 +6,11 @@ namespace Icinga\Controllers; use Exception; use Icinga\Application\Config; use Icinga\Exception\NotFoundError; +use Icinga\Forms\ConfirmRemovalForm; use Icinga\Forms\Navigation\NavigationConfigForm; use Icinga\Web\Controller; use Icinga\Web\Form; +use Icinga\Web\Notification; use Icinga\Web\Url; /** @@ -166,7 +168,32 @@ class NavigationController extends Controller */ public function removeAction() { - + $itemName = $this->params->getRequired('name'); + + $navigationConfigForm = new NavigationConfigForm(); + $navigationConfigForm->setIniConfig($this->Auth()->getUser()->loadNavigationConfig()); + $form = new ConfirmRemovalForm(); + $form->setRedirectUrl('navigation'); + $form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $itemName)); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) { + try { + $navigationConfigForm->delete($itemName); + } catch (Exception $e) { + $form->error($e->getMessage()); + return false; + } + + if ($navigationConfigForm->save()) { + Notification::success(sprintf(t('Navigation Item "%s" successfully removed'), $itemName)); + return true; + } + + return false; + }); + $form->handleRequest(); + + $this->view->form = $form; + $this->render('form'); } /** diff --git a/application/views/scripts/navigation/form.phtml b/application/views/scripts/navigation/form.phtml new file mode 100644 index 000000000..cbf06590d --- /dev/null +++ b/application/views/scripts/navigation/form.phtml @@ -0,0 +1,6 @@ +
        + showOnlyCloseButton(); ?> +
        +
        + +
        \ No newline at end of file From 9dbbc5172157dfd3009603b962a83349fc7175f4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 16:27:27 +0200 Subject: [PATCH 104/225] NavigationController: Allow the owner to edit/remove a shared item refs #5600 --- .../controllers/NavigationController.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 080ef328e..290742ba7 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -121,8 +121,14 @@ class NavigationController extends Controller $user = $this->Auth()->getUser(); $config = $user->loadNavigationConfig(); - if (! $config->hasSection($itemName) && $user->can('config/application/navigation')) { - $config = Config::app('navigation'); + if (! $config->hasSection($itemName)) { + $shareConfig = Config::app('navigation'); + if ($shareConfig->hasSection($itemName) + && ($shareConfig->get($itemName, 'owner') === $user->getUsername() + || $user->can('config/application/navigation')) + ) { + $config = $shareConfig; + } } $form = new NavigationConfigForm(); @@ -169,9 +175,18 @@ class NavigationController extends Controller public function removeAction() { $itemName = $this->params->getRequired('name'); + $user = $this->Auth()->getUser(); + + $config = $user->loadNavigationConfig(); + if (! $config->hasSection($itemName)) { + $shareConfig = Config::app('navigation'); + if ($shareConfig->hasSection($itemName) && $shareConfig->get($itemName, 'owner') === $user->getUsername()) { + $config = $shareConfig; + } + } $navigationConfigForm = new NavigationConfigForm(); - $navigationConfigForm->setIniConfig($this->Auth()->getUser()->loadNavigationConfig()); + $navigationConfigForm->setIniConfig($config); $form = new ConfirmRemovalForm(); $form->setRedirectUrl('navigation'); $form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $itemName)); From 1a2556af135f7f704eb5d46ef206ef414d674499 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 7 Sep 2015 16:58:22 +0200 Subject: [PATCH 105/225] Add class NavigationConfigForm (WIP) refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 260 ++++++++++++++++++ .../forms/Navigation/NavigationItemForm.php | 39 +++ 2 files changed, 299 insertions(+) create mode 100644 application/forms/Navigation/NavigationConfigForm.php create mode 100644 application/forms/Navigation/NavigationItemForm.php diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php new file mode 100644 index 000000000..50d4fc111 --- /dev/null +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -0,0 +1,260 @@ +setName('form_config_navigation'); + $this->setSubmitLabel($this->translate('Save Changes')); + } + + /** + * Populate the form with the given navigation item's config + * + * @param string $name + * + * @return $this + * + * @throws NotFoundError In case no navigation item with the given name is found + */ + public function load($name) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No navigation item called "%s" found', $name); + } + + $this->itemToLoad = $name; + return $this; + } + + /** + * Add a new navigation item + * + * The navigation item to add is identified by the array-key `name'. + * + * @param array $data + * + * @return $this + * + * @throws InvalidArgumentException In case $data does not contain a navigation item name + * @throws IcingaException In case a navigation item with the same name already exists + */ + public function add(array $data) + { + if (! isset($data['name'])) { + throw new InvalidArgumentException('Key \'name\' missing'); + } + + $itemName = $data['name']; + if ($this->config->hasSection($itemName)) { + throw new IcingaException( + $this->translate('A navigation item with the name "%s" does already exist'), + $itemName + ); + } + + unset($data['name']); + $this->config->setSection($itemName, $data); + return $this; + } + + /** + * Edit a navigation item + * + * @param string $name + * @param array $data + * + * @return $this + * + * @throws NotFoundError In case no navigation item with the given name is found + */ + public function edit($name, array $data) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No navigation item called "%s" found', $name); + } + + $itemConfig = $this->config->getSection($name); + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + + unset($data['name']); + } + + $itemConfig->merge($data); + foreach ($itemConfig->toArray() as $k => $v) { + if ($v === null) { + unset($itemConfig->$k); + } + } + + $this->config->setSection($name, $itemConfig); + return $this; + } + + /** + * Remove a navigation item + * + * @param string $name + * + * @return $this + */ + public function delete($name) + { + $this->config->removeSection($name); + return $this; + } + + /** + * Unshare the given navigation item + * + * @param string $name + * + * @return bool + * + * @throws NotFoundError In case no navigation item with the given name is found + */ + public function unshare($name) + { + throw new NotFoundError($name); + } + + /** + * {@inheritdoc} + */ + public function createElements(array $formData) + { + $itemTypes = $this->listItemTypes(); + $itemType = isset($formData['type']) ? $formData['type'] : reset($itemTypes); + + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Name'), + 'description' => $this->translate( + 'The name of this navigation item that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) + ) + ); + + $this->addElement( + 'select', + 'type', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Type'), + 'description' => $this->translate('The type of this navigation item'), + 'multiOptions' => $itemTypes + ) + ); + + $this->addSubForm($this->getItemForm($itemType)->create($formData), 'item_form'); + } + + /** + * Populate the configuration of the navigation item to load + */ + public function onRequest() + { + if ($this->itemToLoad) { + $data = $this->config->getSection($this->itemToLoad)->toArray(); + $data['name'] = $this->itemToLoad; + $this->populate($data); + } + } + + /** + * {@inheritdoc} + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['item_form']); + unset($values['item_form']); + return $values; + } + + /** + * Return a list of available item types + * + * @return array + */ + protected function listItemTypes() + { + $types = $this->defaultItemTypes; + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + $moduleItems = $module->getNavigationItems(); + if (! empty($moduleItems)) { + $types = array_merge($types, $moduleItems); + } + } + + return $types; + } + + /** + * Return the form for the given type of navigation item + * + * @param string $type + * + * @return Form + */ + protected function getItemForm($type) + { + // TODO: Load form classes dynamically + return new NavigationItemForm(); + } +} diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php new file mode 100644 index 000000000..813149d79 --- /dev/null +++ b/application/forms/Navigation/NavigationItemForm.php @@ -0,0 +1,39 @@ +addElement( + 'text', + 'url', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Url'), + 'description' => $this->translate( + 'The url of this navigation item. Leave blank if you only want the name being displayed.' + ) + ) + ); + + $this->addElement( + 'text', + 'icon', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Icon'), + 'description' => $this->translate( + 'The icon of this navigation item. Leave blank if you do not want a icon being displayed.' + ) + ) + ); + } +} From ccae7c4d0d959eb7fb691f20414885d377808964 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 15 Sep 2015 13:54:53 +0200 Subject: [PATCH 106/225] Module: Allow to define a dashboard pane's properties Since dashboards are now alphabetically sorted as well, we need some way to affect this as the old behaviour was to sort them as they were registered refs #5600 --- library/Icinga/Application/Modules/Module.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 911fd058c..29a3b78dc 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -308,9 +308,12 @@ class Module /** @var DashboardContainer $pane */ $navigation->addItem( $pane->getName(), - array( - 'type' => 'dashboard-pane', - 'dashlets' => $pane->getDashlets() + array_merge( + $pane->getProperties(), + array( + 'type' => 'dashboard-pane', + 'dashlets' => $pane->getDashlets() + ) ) ); } @@ -322,12 +325,18 @@ class Module * Add or get a dashboard pane * * @param string $name + * @param array $properties * * @return DashboardContainer */ - protected function dashboard($name) + protected function dashboard($name, array $properties = array()) { - $this->paneItems[$name] = new DashboardContainer($name); + if (array_key_exists($name, $this->paneItems)) { + $this->paneItems[$name]->setProperties($properties); + } else { + $this->paneItems[$name] = new DashboardContainer($name, $properties); + } + return $this->paneItems[$name]; } From bc57f9b613da9d24e7677d9bddcfe167838c2e77 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 15 Sep 2015 13:55:24 +0200 Subject: [PATCH 107/225] monitoring: Provide a priority of 50 to 100 for the default dashboards refs #5600 --- modules/monitoring/configuration.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 6946c7b9a..6e80237e2 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -238,7 +238,7 @@ $section->add($this->translate('Monitoring Health'), array( /* * Current Incidents */ -$dashboard = $this->dashboard($this->translate('Current Incidents')); +$dashboard = $this->dashboard($this->translate('Current Incidents'), array('priority' => 50)); $dashboard->add( $this->translate('Service Problems'), 'monitoring/list/services?service_problem=1&limit=10&sort=service_severity' @@ -255,7 +255,7 @@ $dashboard->add( /* * Overview */ -$dashboard = $this->dashboard($this->translate('Overview')); +$dashboard = $this->dashboard($this->translate('Overview'), array('priority' => 60)); $dashboard->add( $this->translate('Service Grid'), 'monitoring/list/servicegrid?limit=15,18' @@ -272,7 +272,7 @@ $dashboard->add( /* * Most Overdue */ -$dashboard = $this->dashboard($this->translate('Overdue')); +$dashboard = $this->dashboard($this->translate('Overdue'), array('priority' => 70)); $dashboard->add( $this->translate('Late Host Check Results'), 'monitoring/list/hosts?host_next_updateadd( /* * Muted Objects */ -$dashboard = $this->dashboard($this->translate('Muted')); +$dashboard = $this->dashboard($this->translate('Muted'), array('priority' => 80)); $dashboard->add( $this->translate('Disabled Service Notifications'), 'monitoring/list/services?service_notifications_enabled=0&limit=10' @@ -322,7 +322,7 @@ $dashboard->add( /* * Activity Stream */ -$dashboard = $this->dashboard($this->translate('Activity Stream')); +$dashboard = $this->dashboard($this->translate('Activity Stream'), array('priority' => 90)); $dashboard->add( $this->translate('Recent Events'), 'monitoring/list/eventhistory?timestamp>=-3%20days&sort=timestamp&dir=desc&limit=8' @@ -347,7 +347,7 @@ $dashboard->add( /* * Stats */ -$dashboard = $this->dashboard($this->translate('Stats')); +$dashboard = $this->dashboard($this->translate('Stats'), array('priority' => 100)); $dashboard->add( $this->translate('Check Stats'), 'monitoring/health/stats' From d4c0f10b47d7446664499949d0dc5d79941962ab Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 15 Sep 2015 15:57:03 +0200 Subject: [PATCH 108/225] NavigationConfigForm: Let the form decide which configuration to manage refs #5600 --- .../controllers/NavigationController.php | 36 ++--- .../forms/Navigation/NavigationConfigForm.php | 147 ++++++++++++++++-- 2 files changed, 148 insertions(+), 35 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 290742ba7..de999a9e9 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -90,7 +90,8 @@ class NavigationController extends Controller $form->setRedirectUrl('navigation'); $form->setTitle($this->translate('Create New Navigation Item')); $form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.')); - $form->setIniConfig($this->Auth()->getUser()->loadNavigationConfig()); + $form->setUser($this->Auth()->getUser()); + $form->setShareConfig(Config::app('navigation')); $form->setOnSuccess(function (NavigationConfigForm $form) { try { $form->add(array_filter($form->getValues())); @@ -118,23 +119,12 @@ class NavigationController extends Controller public function editAction() { $itemName = $this->params->getRequired('name'); - $user = $this->Auth()->getUser(); - - $config = $user->loadNavigationConfig(); - if (! $config->hasSection($itemName)) { - $shareConfig = Config::app('navigation'); - if ($shareConfig->hasSection($itemName) - && ($shareConfig->get($itemName, 'owner') === $user->getUsername() - || $user->can('config/application/navigation')) - ) { - $config = $shareConfig; - } - } $form = new NavigationConfigForm(); - $form->setIniConfig($config); $form->setRedirectUrl('navigation'); $form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName)); + $form->setUser($this->Auth()->getUser()); + $form->setShareConfig(Config::app('navigation')); $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) { try { $form->edit($itemName, array_map( @@ -175,24 +165,19 @@ class NavigationController extends Controller public function removeAction() { $itemName = $this->params->getRequired('name'); - $user = $this->Auth()->getUser(); - - $config = $user->loadNavigationConfig(); - if (! $config->hasSection($itemName)) { - $shareConfig = Config::app('navigation'); - if ($shareConfig->hasSection($itemName) && $shareConfig->get($itemName, 'owner') === $user->getUsername()) { - $config = $shareConfig; - } - } $navigationConfigForm = new NavigationConfigForm(); - $navigationConfigForm->setIniConfig($config); + $navigationConfigForm->setUser($this->Auth()->getUser()); + $navigationConfigForm->setShareConfig(Config::app('navigation')); $form = new ConfirmRemovalForm(); $form->setRedirectUrl('navigation'); $form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $itemName)); $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) { try { $navigationConfigForm->delete($itemName); + } catch (NotFoundError $e) { + Notification::success(sprintf(t('Navigation Item "%s" not found. No action required'), $itemName)); + return true; } catch (Exception $e) { $form->error($e->getMessage()); return false; @@ -220,7 +205,8 @@ class NavigationController extends Controller $this->assertHttpMethod('POST'); $navigationConfigForm = new NavigationConfigForm(); - $navigationConfigForm->setIniConfig(Config::app('navigation')); + $navigationConfigForm->setUser($this->Auth()->getUser()); + $navigationConfigForm->setShareConfig(Config::app('navigation')); $form = new Form(array( 'onSuccess' => function ($form) use ($navigationConfigForm) { diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 50d4fc111..96d8ac9c6 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -4,10 +4,12 @@ namespace Icinga\Forms\Navigation; use InvalidArgumentException; +use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfigForm; +use Icinga\User; use Icinga\Web\Form; /** @@ -32,6 +34,27 @@ class NavigationConfigForm extends ConfigForm */ protected $itemToLoad; + /** + * The user for whom to manage navigation items + * + * @var User + */ + protected $user; + + /** + * The user's navigation configuration + * + * @var Config + */ + protected $userConfig; + + /** + * The shared navigation configuration + * + * @var Config + */ + protected $shareConfig; + /** * Initialize this form */ @@ -41,6 +64,79 @@ class NavigationConfigForm extends ConfigForm $this->setSubmitLabel($this->translate('Save Changes')); } + /** + * Set the user for whom to manage navigation items + * + * @param User $user + * + * @return $this + */ + public function setUser(User $user) + { + $this->user = $user; + return $this; + } + + /** + * Return the user for whom to manage navigation items + * + * @return User + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the user's navigation configuration + * + * @param Config $config + * + * @return $this + */ + public function setUserConfig(Config $config) + { + $this->userConfig = $config; + return $this; + } + + /** + * Return the user's navigation configuration + * + * @return Config + */ + public function getUserConfig() + { + if ($this->userConfig === null) { + $this->userConfig = $this->getUser()->loadNavigationConfig(); + } + + return $this->userConfig; + } + + /** + * Set the shared navigation configuration + * + * @param Config $config + * + * @return $this + */ + public function setShareConfig(Config $config) + { + $this->shareConfig = $config; + return $this; + } + + /** + * Return the shared navigation configuration + * + * @return Config + */ + public function getShareConfig() + { + return $this->shareConfig; + } + /** * Populate the form with the given navigation item's config * @@ -52,7 +148,7 @@ class NavigationConfigForm extends ConfigForm */ public function load($name) { - if (! $this->config->hasSection($name)) { + if ($this->getConfigForItem($name) === null) { throw new NotFoundError('No navigation item called "%s" found', $name); } @@ -79,7 +175,8 @@ class NavigationConfigForm extends ConfigForm } $itemName = $data['name']; - if ($this->config->hasSection($itemName)) { + $config = $this->getUserConfig(); + if ($config->hasSection($itemName)) { throw new IcingaException( $this->translate('A navigation item with the name "%s" does already exist'), $itemName @@ -87,7 +184,8 @@ class NavigationConfigForm extends ConfigForm } unset($data['name']); - $this->config->setSection($itemName, $data); + $config->setSection($itemName, $data); + $this->setIniConfig($config); return $this; } @@ -103,14 +201,15 @@ class NavigationConfigForm extends ConfigForm */ public function edit($name, array $data) { - if (! $this->config->hasSection($name)) { + $config = $this->getConfigForItem($name); + if ($config === null) { throw new NotFoundError('No navigation item called "%s" found', $name); } - $itemConfig = $this->config->getSection($name); + $itemConfig = $config->getSection($name); if (isset($data['name'])) { if ($data['name'] !== $name) { - $this->config->removeSection($name); + $config->removeSection($name); $name = $data['name']; } @@ -124,7 +223,8 @@ class NavigationConfigForm extends ConfigForm } } - $this->config->setSection($name, $itemConfig); + $config->setSection($name, $itemConfig); + $this->setIniConfig($config); return $this; } @@ -137,7 +237,13 @@ class NavigationConfigForm extends ConfigForm */ public function delete($name) { - $this->config->removeSection($name); + $config = $this->getConfigForItem($name); + if ($config === null) { + throw new NotFoundError('No navigation item called "%s" found', $name); + } + + $config->removeSection($name); + $this->setIniConfig($config); return $this; } @@ -152,7 +258,7 @@ class NavigationConfigForm extends ConfigForm */ public function unshare($name) { - throw new NotFoundError($name); + throw new NotFoundError('No navigation item called "%s" found', $name); } /** @@ -210,7 +316,7 @@ class NavigationConfigForm extends ConfigForm public function onRequest() { if ($this->itemToLoad) { - $data = $this->config->getSection($this->itemToLoad)->toArray(); + $data = $this->getConfigForItem($this->itemToLoad)->getSection($this->itemToLoad)->toArray(); $data['name'] = $this->itemToLoad; $this->populate($data); } @@ -245,6 +351,27 @@ class NavigationConfigForm extends ConfigForm return $types; } + /** + * Return the navigation configuration the given item is a part of + * + * @param string $name + * + * @return Config|null In case the item is not part of any configuration + */ + protected function getConfigForItem($name) + { + if ($this->getUserConfig()->hasSection($name)) { + return $this->getUserConfig(); + } elseif ($this->getShareConfig()->hasSection($name)) { + if ( + $this->getShareConfig()->get($name, 'owner') === $this->getUser()->getUsername() + || $this->getUser()->can('config/application/navigation') + ) { + return $this->getShareConfig(); + } + } + } + /** * Return the form for the given type of navigation item * From d0eb61dc2ed18bacd11eb1c8abc07bbaf2d4e610 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 15 Sep 2015 16:09:33 +0200 Subject: [PATCH 109/225] Form: Add property $parent refs #5600 --- library/Icinga/Web/Form.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 1e42e8ba6..f3a2c8c80 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -65,6 +65,15 @@ class Form extends Zend_Form */ protected $created = false; + /** + * This form's parent + * + * Gets automatically set upon calling addSubForm(). + * + * @var Form + */ + protected $parent; + /** * Whether the form is an API target * @@ -243,6 +252,29 @@ class Form extends Zend_Form parent::__construct($options); } + /** + * Set this form's parent + * + * @param Form $form + * + * @return $this + */ + public function setParent(Form $form) + { + $this->parent = $form; + return $this; + } + + /** + * Return this form's parent + * + * @return Form + */ + public function getParent() + { + return $this->parent; + } + /** * Set a callback that is called instead of this form's onSuccess method * @@ -844,6 +876,7 @@ class Form extends Zend_Form $form->setSubmitLabel(''); $form->setTokenDisabled(); $form->setUidDisabled(); + $form->setParent($this); } if ($name === null) { From b96c89339bbca234bd341a6ecbb98c5e982db744 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 15 Sep 2015 16:16:32 +0200 Subject: [PATCH 110/225] NavigationConfigForm: Do not use a sequenced array to initialize the select refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 96d8ac9c6..3e51d6618 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -303,7 +303,7 @@ class NavigationConfigForm extends ConfigForm 'autosubmit' => true, 'label' => $this->translate('Type'), 'description' => $this->translate('The type of this navigation item'), - 'multiOptions' => $itemTypes + 'multiOptions' => array_combine($itemTypes, $itemTypes) ) ); From 402440735c931bea87d3179ea7ea1d6ef0d71092 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 08:30:40 +0200 Subject: [PATCH 111/225] RoleForm: Add permission "application/share/navigation" refs #5600 --- application/forms/Security/RoleForm.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 63bd459ce..16fabf78c 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -37,6 +37,8 @@ class RoleForm extends ConfigForm { $this->providedPermissions = array( '*' => $this->translate('Allow everything') . ' (*)', + 'application/share/navigation' => $this->translate('Allow to share navigation items') + . ' (application/share/navigation)', 'application/stacktraces' => $this->translate( 'Allow to adjust in the preferences whether to show stacktraces' ) . ' (application/stacktraces)', @@ -48,6 +50,7 @@ class RoleForm extends ConfigForm 'config/application/resources' => 'config/application/resources', 'config/application/userbackend' => 'config/application/userbackend', 'config/application/usergroupbackend' => 'config/application/usergroupbackend', + 'config/application/navigation' => 'config/application/navigation', 'config/authentication/*' => 'config/authentication/*', 'config/authentication/users/*' => 'config/authentication/users/*', 'config/authentication/users/show' => 'config/authentication/users/show', From 19945d074c4161db61302e343d16ba74176a7eaf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 09:09:45 +0200 Subject: [PATCH 112/225] forms.less: Properly align checkboxes and autosubmit warnings It's far from perfect, but it looks nearly the same in FF and Chrome.. --- public/css/icinga/forms.less | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index 2ab93f869..f149c3272 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -36,6 +36,11 @@ input, select, textarea { font-family: Calibri, Helvetica, sans-serif; } +input[type=checkbox] { + margin-top: 0.3em; + margin-bottom: 0.1em; +} + input:focus, select:focus { border-color: #333; } @@ -360,6 +365,10 @@ i.autosubmit-warning { } } +input[type=checkbox] + i.autosubmit-warning { + margin-top: 0.15em; +} + html.no-js i.autosubmit-warning { .sr-only; } From 50cdd0ad15586a3305e71d8be150ed00dc9d1a16 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 09:33:43 +0200 Subject: [PATCH 113/225] NavigationConfigForm: Do not validate an item's name refs #5600 refs #10151 --- .../forms/Navigation/NavigationConfigForm.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 3e51d6618..097ea71b0 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -277,20 +277,6 @@ class NavigationConfigForm extends ConfigForm 'label' => $this->translate('Name'), 'description' => $this->translate( 'The name of this navigation item that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); From ac3ef393b58a12467ff2747fe592e23dd9bfcfce Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 10:58:57 +0200 Subject: [PATCH 114/225] NavigationConfigForm: Write both configurations in a single "operation" refs #5600 --- .../controllers/NavigationController.php | 26 +++++++++++-------- application/forms/ConfigForm.php | 12 ++++++++- .../forms/Navigation/NavigationConfigForm.php | 23 ++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index de999a9e9..b1bcb3309 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -211,23 +211,27 @@ class NavigationController extends Controller $form = new Form(array( 'onSuccess' => function ($form) use ($navigationConfigForm) { try { - if ($navigationConfigForm->unshare($form->getValue('name'))) { - Notification::success(sprintf( - t('Navigation item "%s" has been unshared'), - $form->getValue('name') - )); - } else { - Notification::error(sprintf( - t('Failed to unshare navigation item "%s"'), - $form->getValue('name') - )); - } + $navigationConfigForm->unshare($form->getValue('name')); } catch (NotFoundError $e) { throw $e; } catch (Exception $e) { Notification::error($e->getMessage()); } + if ($navigationConfigForm->save()) { + Notification::success(sprintf( + t('Navigation item "%s" has been unshared'), + $form->getValue('name') + )); + } else { + // TODO: It failed obviously to write one of the configs, so we're leaving the user in + // a inconsistent state. Luckily, it's nothing lost but possibly duplicated... + Notification::error(sprintf( + t('Failed to unshare navigation item "%s"'), + $form->getValue('name') + )); + } + $redirect = $form->getValue('redirect'); if (! empty($redirect)) { $form->setRedirectUrl(htmlspecialchars_decode($redirect)); diff --git a/application/forms/ConfigForm.php b/application/forms/ConfigForm.php index 58d550793..3f3ba227f 100644 --- a/application/forms/ConfigForm.php +++ b/application/forms/ConfigForm.php @@ -43,7 +43,7 @@ class ConfigForm extends Form public function save() { try { - $this->config->saveIni(); + $this->writeConfig($this->config); } catch (Exception $e) { $this->addDecorator('ViewScript', array( 'viewModule' => 'default', @@ -58,4 +58,14 @@ class ConfigForm extends Form return true; } + + /** + * Write the configuration to disk + * + * @param Config $config + */ + protected function writeConfig(Config $config) + { + $config->saveIni(); + } } diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 097ea71b0..495055cab 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -27,6 +27,16 @@ class NavigationConfigForm extends ConfigForm 'dashlet' ); + /** + * The secondary configuration to write + * + * This is always the reduced configuration and is only written to + * disk once the main configuration has been successfully written. + * + * @var Config + */ + protected $secondaryConfig; + /** * The navigation item to load when displaying the form for the first time * @@ -319,6 +329,19 @@ class NavigationConfigForm extends ConfigForm return $values; } + /** + * {@inheritdoc} + */ + protected function writeConfig(Config $config) + { + parent::writeConfig($config); + + if ($this->secondaryConfig !== null) { + $this->config = $this->secondaryConfig; // Causes the config being displayed to the user in case of an error + parent::writeConfig($this->secondaryConfig); + } + } + /** * Return a list of available item types * From fc5f0f05308e01d727664f36aa6679b52a0f3356 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 11:51:57 +0200 Subject: [PATCH 115/225] NavigationConfigForm: Make it possible to share items with others refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 495055cab..84bef9669 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -184,8 +184,18 @@ class NavigationConfigForm extends ConfigForm throw new InvalidArgumentException('Key \'name\' missing'); } - $itemName = $data['name']; $config = $this->getUserConfig(); + if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { + if ($this->getUser()->can('application/share/navigation')) { + $data['owner'] = $this->getUser()->getUsername(); + $config = $this->getShareConfig(); + } else { + unset($data['users']); + unset($data['groups']); + } + } + + $itemName = $data['name']; if ($config->hasSection($itemName)) { throw new IcingaException( $this->translate('A navigation item with the name "%s" does already exist'), @@ -217,6 +227,37 @@ class NavigationConfigForm extends ConfigForm } $itemConfig = $config->getSection($name); + + if ($this->hasBeenShared($name)) { + if ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) { + // It is shared but shouldn't anymore + $config->removeSection($name); + $this->secondaryConfig = $config; + + if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) { + $config = $this->getUserConfig(); + } else { + $owner = new User($itemConfig->owner); + $config = $owner->loadNavigationConfig(); + } + + unset($itemConfig->owner); + unset($itemConfig->users); + unset($itemConfig->groups); + } + } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { + if ($this->getUser()->can('application/share/navigation')) { + // It is not shared yet but should be + $config->removeSection($name); + $this->secondaryConfig = $config; + $config = $this->getShareConfig(); + $data['owner'] = $this->getUser()->getUsername(); + } else { + unset($data['users']); + unset($data['groups']); + } + } + if (isset($data['name'])) { if ($data['name'] !== $name) { $config->removeSection($name); @@ -291,6 +332,45 @@ class NavigationConfigForm extends ConfigForm ) ); + if ($this->getUser()->can('application/share/navigation')) { + $checked = isset($formData['shared']) ? null : (isset($formData['users']) || isset($formData['groups'])); + + $this->addElement( + 'checkbox', + 'shared', + array( + 'autosubmit' => true, + 'ignore' => true, + 'value' => $checked, + 'label' => $this->translate('Shared'), + 'description' => $this->translate('Tick this box to share this item with others') + ) + ); + + if ($checked || (isset($formData['shared']) && $formData['shared'])) { + $this->addElement( + 'text', + 'users', + array( + 'label' => $this->translate('Users'), + 'description' => $this->translate( + 'Comma separated list of usernames to share this item with' + ) + ) + ); + $this->addElement( + 'text', + 'groups', + array( + 'label' => $this->translate('Groups'), + 'description' => $this->translate( + 'Comma separated list of group names to share this item with' + ) + ) + ); + } + } + $this->addElement( 'select', 'type', @@ -381,6 +461,18 @@ class NavigationConfigForm extends ConfigForm } } + /** + * Return whether the given navigation item has been shared + * + * @param string $name + * + * @return bool + */ + protected function hasBeenShared($name) + { + return $this->getConfigForItem($name) === $this->getShareConfig(); + } + /** * Return the form for the given type of navigation item * From 5d9a57446f86804d924d0e9c3196bbddbe07ac41 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 11:52:42 +0200 Subject: [PATCH 116/225] shared navigation overview: Display the owner of a shared navigation item refs #5600 --- application/views/scripts/navigation/shared.phtml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index 657495ca3..131100dae 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -7,6 +7,7 @@ + @@ -20,6 +21,7 @@ 'title' => sprintf($this->translate('Edit shared navigation item %s'), $name) ) ); ?> + From 16a57c5c1bd5b0b3515c051435426e4e10c0958d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:05:45 +0200 Subject: [PATCH 117/225] shared navigation overview: We're unsharing items, not removing them refs #5600 --- application/views/scripts/navigation/shared.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index 131100dae..a9c93afee 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -8,7 +8,7 @@ - + $item): ?> From 4db2f7e7a95dad8809a9078e71206212fa1473df Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:07:41 +0200 Subject: [PATCH 118/225] shared navigation overview: Show a message if there's nothing shared refs #5600 --- application/views/scripts/navigation/shared.phtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index a9c93afee..f4c0ec890 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -4,6 +4,9 @@
        +isEmpty()): ?> + translate('There are currently no navigation items being shared'); ?> +
        translate('Shared Navigation'); ?>translate('Owner'); ?> translate('Remove'); ?>
        escape($item->owner); ?> setDefault('name', $name); ?>
        translate('Shared Navigation'); ?> translate('Owner'); ?>translate('Remove'); ?>translate('Unshare'); ?>
        @@ -27,4 +30,5 @@
        translate('Shared Navigation'); ?>
        + \ No newline at end of file From cc66175e6c15411f21085549fd37ca070f8509c8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:08:44 +0200 Subject: [PATCH 119/225] shared navigation overview: Do not open a new column while unsharing refs #5600 --- application/views/scripts/navigation/shared.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index f4c0ec890..c7bcb7d0e 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -25,7 +25,7 @@ ) ); ?> escape($item->owner); ?> - setDefault('name', $name); ?> + setDefault('name', $name); ?> From dfc9c105895764dd882fe83be6771982c93726e9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:10:21 +0200 Subject: [PATCH 120/225] NavigationConfigForm: Implement method unshare() refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 84bef9669..bd58edb20 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -231,19 +231,7 @@ class NavigationConfigForm extends ConfigForm if ($this->hasBeenShared($name)) { if ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) { // It is shared but shouldn't anymore - $config->removeSection($name); - $this->secondaryConfig = $config; - - if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) { - $config = $this->getUserConfig(); - } else { - $owner = new User($itemConfig->owner); - $config = $owner->loadNavigationConfig(); - } - - unset($itemConfig->owner); - unset($itemConfig->users); - unset($itemConfig->groups); + $config = $this->unshare($name)->config; // unshare() calls setIniConfig() } } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ($this->getUser()->can('application/share/navigation')) { @@ -303,13 +291,35 @@ class NavigationConfigForm extends ConfigForm * * @param string $name * - * @return bool + * @return $this * * @throws NotFoundError In case no navigation item with the given name is found */ public function unshare($name) { - throw new NotFoundError('No navigation item called "%s" found', $name); + $config = $this->getShareConfig(); + if (! $config->hasSection($name)) { + throw new NotFoundError('No navigation item called "%s" found', $name); + } + + $itemConfig = $config->getSection($name); + $config->removeSection($name); + $this->secondaryConfig = $config; + + if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) { + $config = $this->getUserConfig(); + } else { + $owner = new User($itemConfig->owner); + $config = $owner->loadNavigationConfig(); + } + + unset($itemConfig->owner); + unset($itemConfig->users); + unset($itemConfig->groups); + + $config->setSection($name, $itemConfig); + $this->setIniConfig($config); + return $this; } /** From ffb54e0ecb5283b83746ad7df3c4a63278830897 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:32:20 +0200 Subject: [PATCH 121/225] monitoring: Do not show bullet points for actions and notes refs #5600 --- .../application/views/scripts/show/components/actions.phtml | 2 +- .../application/views/scripts/show/components/notes.phtml | 2 +- modules/monitoring/public/css/module.less | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index 1f39f6996..c0d40c4bc 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -34,5 +34,5 @@ if ($navigation->isEmpty()) { ?> translate('Actions'); ?> - getRenderer()->setElementTag('td'); ?> + getRenderer()->setElementTag('td')->setCssClass('actions'); ?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml index c5179dcfa..64f4a491d 100644 --- a/modules/monitoring/application/views/scripts/show/components/notes.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml @@ -38,5 +38,5 @@ if ($navigation->isEmpty()) { ?> translate('Notes'); ?> - getRenderer()->setElementTag('td'); ?> + getRenderer()->setElementTag('td')->setCssClass('notes'); ?> \ No newline at end of file diff --git a/modules/monitoring/public/css/module.less b/modules/monitoring/public/css/module.less index 4eb672c32..405d2a6aa 100644 --- a/modules/monitoring/public/css/module.less +++ b/modules/monitoring/public/css/module.less @@ -17,6 +17,11 @@ table.action td .pluginoutput { margin: 0; } +td.notes ul.nav, +td.actions ul.nav { + .non-list-like-list; +} + div.pluginoutput { overflow: auto; color: #888; From 2cd774018c783e9d2a92a13a8eb9ab5a0ecfffc6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:51:12 +0200 Subject: [PATCH 122/225] NavigationConfigForm: Provide proper labels for navigation item types refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index bd58edb20..d758222cc 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -22,10 +22,7 @@ class NavigationConfigForm extends ConfigForm * * @var array */ - protected $defaultItemTypes = array( - 'menu-item', - 'dashlet' - ); + protected $defaultItemTypes; /** * The secondary configuration to write @@ -72,6 +69,11 @@ class NavigationConfigForm extends ConfigForm { $this->setName('form_config_navigation'); $this->setSubmitLabel($this->translate('Save Changes')); + + $this->defaultItemTypes = array( + 'menu-item' => $this->translate('Menu Entry'), + 'dashlet' => 'Dashlet' + ); } /** @@ -328,7 +330,7 @@ class NavigationConfigForm extends ConfigForm public function createElements(array $formData) { $itemTypes = $this->listItemTypes(); - $itemType = isset($formData['type']) ? $formData['type'] : reset($itemTypes); + $itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes); $this->addElement( 'text', @@ -389,7 +391,7 @@ class NavigationConfigForm extends ConfigForm 'autosubmit' => true, 'label' => $this->translate('Type'), 'description' => $this->translate('The type of this navigation item'), - 'multiOptions' => array_combine($itemTypes, $itemTypes) + 'multiOptions' => $itemTypes ) ); @@ -440,10 +442,9 @@ class NavigationConfigForm extends ConfigForm protected function listItemTypes() { $types = $this->defaultItemTypes; - foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { - $moduleItems = $module->getNavigationItems(); - if (! empty($moduleItems)) { - $types = array_merge($types, $moduleItems); + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $moduleName => $module) { + foreach ($module->getNavigationItems() as $type => $label) { + $types[$type] = mt($moduleName, $label); } } From 0ca6e04a5a85a1cd286d33d109b2bb604c1d1189 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 13:51:35 +0200 Subject: [PATCH 123/225] Module: Allow to pass a label for a custom navigation item type refs #5600 --- library/Icinga/Application/Modules/Module.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 29a3b78dc..f8a1b3d05 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -1007,15 +1007,16 @@ class Module } /** - * Provide a new type of configurable navigation item + * Provide a new type of configurable navigation item with a optional label * * @param string $type + * @param string $label * * @return $this */ - protected function provideNavigationItem($type) + protected function provideNavigationItem($type, $label = null) { - $this->navigationItems[] = $type; + $this->navigationItems[$type] = $label ?: $type; return $this; } From c815a2b486d5d84c3d136031b3d82b0dd4554c44 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 14:05:34 +0200 Subject: [PATCH 124/225] monitoring: Provide a label for all custom navigation item types refs #5600 --- modules/monitoring/configuration.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 6e80237e2..5cf8f8be7 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -88,10 +88,10 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv /* * Available navigation items */ -$this->provideNavigationItem('host-action'); -$this->provideNavigationItem('service-action'); -$this->provideNavigationItem('host-note'); -$this->provideNavigationItem('service-note'); +$this->provideNavigationItem('host-action', $this->translate('Host Action')); +$this->provideNavigationItem('service-action', $this->translate('Service Action')); +$this->provideNavigationItem('host-note', $this->translate('Host Note')); +$this->provideNavigationItem('service-note', $this->translate('Service Note')); /* * Problems Section From bbadb0b75c6feac679badf42b65ba73f261bb887 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 14:16:40 +0200 Subject: [PATCH 125/225] Add support for no-op translations refs #5600 --- library/Icinga/Application/functions.php | 17 +++++++++++++++++ .../Util/GettextTranslationHelper.php | 1 + 2 files changed, 18 insertions(+) diff --git a/library/Icinga/Application/functions.php b/library/Icinga/Application/functions.php index 3ae0c7ba9..0ec120a8c 100644 --- a/library/Icinga/Application/functions.php +++ b/library/Icinga/Application/functions.php @@ -3,6 +3,23 @@ use Icinga\Util\Translator; + +/** + * No-op translate + * + * Supposed to be used for marking a string as available for translation without actually translating it immediately. + * The returned string is the one given in the input. This does only work with the standard gettext macros t() and mt(). + * + * @param string $messageId + * + * @return string + */ +function N_($messageId) +{ + return $messageId; +} + + if (extension_loaded('gettext')) { /** diff --git a/modules/translation/library/Translation/Util/GettextTranslationHelper.php b/modules/translation/library/Translation/Util/GettextTranslationHelper.php index c3a6d206e..142de8db2 100644 --- a/modules/translation/library/Translation/Util/GettextTranslationHelper.php +++ b/modules/translation/library/Translation/Util/GettextTranslationHelper.php @@ -290,6 +290,7 @@ class GettextTranslationHelper '--keyword=t:1,2c', '--keyword=tp:1,2', '--keyword=tp:1,2,4c', + '--keyword=N_', '--sort-output', '--force-po', '--omit-header', From 8b7a1ce28f5720acf20111ab7bf5246739e1ea48 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 14:29:33 +0200 Subject: [PATCH 126/225] Late translate a module's dashboard-pane/dashlet labels refs #5600 --- library/Icinga/Application/Modules/Module.php | 8 ++- library/Icinga/Web/Widget/Dashboard.php | 2 +- modules/monitoring/configuration.php | 58 +++++++++---------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index f8a1b3d05..16573de99 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -306,13 +306,19 @@ class Module $navigation = new Navigation(); foreach ($panes as $pane) { /** @var DashboardContainer $pane */ + $dashlets = array(); + foreach ($pane->getDashlets() as $dashletName => $dashletUrl) { + $dashlets[$this->translate($dashletName)] = $dashletUrl; + } + $navigation->addItem( $pane->getName(), array_merge( $pane->getProperties(), array( + 'label' => $this->translate($pane->getName()), 'type' => 'dashboard-pane', - 'dashlets' => $pane->getDashlets() + 'dashlets' => $dashlets ) ) ); diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 60ac28e1d..c69fb4477 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -76,7 +76,7 @@ class Dashboard extends AbstractWidget $panes = array(); foreach ($navigation as $dashboardPane) { /** @var DashboardPane $dashboardPane */ - $pane = new Pane($dashboardPane->getName()); + $pane = new Pane($dashboardPane->getLabel()); foreach ($dashboardPane->getDashlets() as $title => $url) { $pane->addDashlet($title, $url); } diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 5cf8f8be7..91eb3e47b 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -238,122 +238,122 @@ $section->add($this->translate('Monitoring Health'), array( /* * Current Incidents */ -$dashboard = $this->dashboard($this->translate('Current Incidents'), array('priority' => 50)); +$dashboard = $this->dashboard(N_('Current Incidents'), array('priority' => 50)); $dashboard->add( - $this->translate('Service Problems'), + N_('Service Problems'), 'monitoring/list/services?service_problem=1&limit=10&sort=service_severity' ); $dashboard->add( - $this->translate('Recently Recovered Services'), + N_('Recently Recovered Services'), 'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc' ); $dashboard->add( - $this->translate('Host Problems'), + N_('Host Problems'), 'monitoring/list/hosts?host_problem=1&sort=host_severity' ); /* * Overview */ -$dashboard = $this->dashboard($this->translate('Overview'), array('priority' => 60)); +$dashboard = $this->dashboard(N_('Overview'), array('priority' => 60)); $dashboard->add( - $this->translate('Service Grid'), + N_('Service Grid'), 'monitoring/list/servicegrid?limit=15,18' ); $dashboard->add( - $this->translate('Service Groups'), + N_('Service Groups'), '/monitoring/list/servicegroups' ); $dashboard->add( - $this->translate('Host Groups'), + N_('Host Groups'), '/monitoring/list/hostgroups' ); /* * Most Overdue */ -$dashboard = $this->dashboard($this->translate('Overdue'), array('priority' => 70)); +$dashboard = $this->dashboard(N_('Overdue'), array('priority' => 70)); $dashboard->add( - $this->translate('Late Host Check Results'), + N_('Late Host Check Results'), 'monitoring/list/hosts?host_next_updateadd( - $this->translate('Late Service Check Results'), + N_('Late Service Check Results'), 'monitoring/list/services?service_next_updateadd( - $this->translate('Acknowledgements Active For At Least Three Days'), + N_('Acknowledgements Active For At Least Three Days'), 'monitoring/list/comments?comment_type=Ack&comment_timestamp<-3 days&sort=comment_timestamp&dir=asc' ); $dashboard->add( - $this->translate('Downtimes Active For More Than Three Days'), + N_('Downtimes Active For More Than Three Days'), 'monitoring/list/downtimes?downtime_is_in_effect=1&downtime_scheduled_start<-3%20days&sort=downtime_start&dir=asc' ); /* * Muted Objects */ -$dashboard = $this->dashboard($this->translate('Muted'), array('priority' => 80)); +$dashboard = $this->dashboard(N_('Muted'), array('priority' => 80)); $dashboard->add( - $this->translate('Disabled Service Notifications'), + N_('Disabled Service Notifications'), 'monitoring/list/services?service_notifications_enabled=0&limit=10' ); $dashboard->add( - $this->translate('Disabled Host Notifications'), + N_('Disabled Host Notifications'), 'monitoring/list/hosts?host_notifications_enabled=0&limit=10' ); $dashboard->add( - $this->translate('Disabled Service Checks'), + N_('Disabled Service Checks'), 'monitoring/list/services?service_active_checks_enabled=0&limit=10' ); $dashboard->add( - $this->translate('Disabled Host Checks'), + N_('Disabled Host Checks'), 'monitoring/list/hosts?host_active_checks_enabled=0&limit=10' ); $dashboard->add( - $this->translate('Acknowledged Problem Services'), + N_('Acknowledged Problem Services'), 'monitoring/list/services?service_acknowledgement_type=2&service_problem=1&sort=service_state&limit=10' ); $dashboard->add( - $this->translate('Acknowledged Problem Hosts'), + N_('Acknowledged Problem Hosts'), 'monitoring/list/hosts?host_acknowledgement_type=2&host_problem=1&sort=host_severity&limit=10' ); /* * Activity Stream */ -$dashboard = $this->dashboard($this->translate('Activity Stream'), array('priority' => 90)); +$dashboard = $this->dashboard(N_('Activity Stream'), array('priority' => 90)); $dashboard->add( - $this->translate('Recent Events'), + N_('Recent Events'), 'monitoring/list/eventhistory?timestamp>=-3%20days&sort=timestamp&dir=desc&limit=8' ); $dashboard->add( - $this->translate('Recent Hard State Changes'), + N_('Recent Hard State Changes'), 'monitoring/list/eventhistory?timestamp>=-3%20days&type=hard_state&sort=timestamp&dir=desc&limit=8' ); $dashboard->add( - $this->translate('Recent Notifications'), + N_('Recent Notifications'), 'monitoring/list/eventhistory?timestamp>=-3%20days&type=notify&sort=timestamp&dir=desc&limit=8' ); $dashboard->add( - $this->translate('Downtimes Recently Started'), + N_('Downtimes Recently Started'), 'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_start&sort=timestamp&dir=desc&limit=8' ); $dashboard->add( - $this->translate('Downtimes Recently Ended'), + N_('Downtimes Recently Ended'), 'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_end&sort=timestamp&dir=desc&limit=8' ); /* * Stats */ -$dashboard = $this->dashboard($this->translate('Stats'), array('priority' => 100)); +$dashboard = $this->dashboard(N_('Stats'), array('priority' => 100)); $dashboard->add( - $this->translate('Check Stats'), + N_('Check Stats'), 'monitoring/health/stats' ); $dashboard->add( - $this->translate('Process Information'), + N_('Process Information'), 'monitoring/health/info' ); From 8c173647807b7788c2e9c4b5cd6f93127324f6af Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 14:36:35 +0200 Subject: [PATCH 127/225] Late translate a module's menu section labels refs #5600 --- library/Icinga/Application/Modules/Module.php | 1 + modules/monitoring/configuration.php | 52 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index 16573de99..ebbfc3d98 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -371,6 +371,7 @@ class Module /** @var MenuItemContainer $item */ $navigationItem = $navigation->createItem($item->getName(), $item->getProperties()); $navigationItem->setChildren($this->createMenu($item->getChildren())); + $navigationItem->setLabel($this->translate($item->getName())); $navigation->addItem($navigationItem); } diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 91eb3e47b..2398ed337 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -96,7 +96,7 @@ $this->provideNavigationItem('service-note', $this->translate('Service Note')); /* * Problems Section */ -$section = $this->menuSection($this->translate('Problems'), array( +$section = $this->menuSection(N_('Problems'), array( 'renderer' => array( 'SummaryNavigationItemRenderer', 'state' => 'critical' @@ -104,7 +104,7 @@ $section = $this->menuSection($this->translate('Problems'), array( 'icon' => 'block', 'priority' => 20 )); -$section->add($this->translate('Unhandled Hosts'), array( +$section->add(N_('Unhandled Hosts'), array( 'renderer' => array( 'MonitoringBadgeNavigationItemRenderer', 'columns' => array( @@ -116,7 +116,7 @@ $section->add($this->translate('Unhandled Hosts'), array( 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', 'priority' => 30 )); -$section->add($this->translate('Unhandled Services'), array( +$section->add(N_('Unhandled Services'), array( 'renderer' => array( 'MonitoringBadgeNavigationItemRenderer', 'columns' => array( @@ -128,19 +128,19 @@ $section->add($this->translate('Unhandled Services'), array( 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', 'priority' => 40 )); -$section->add($this->translate('Host Problems'), array( +$section->add(N_('Host Problems'), array( 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity', 'priority' => 50 )); -$section->add($this->translate('Service Problems'), array( +$section->add(N_('Service Problems'), array( 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc', 'priority' => 60 )); -$section->add($this->translate('Service Grid'), array( +$section->add(N_('Service Grid'), array( 'url' => 'monitoring/list/servicegrid?problems', 'priority' => 70 )); -$section->add($this->translate('Current Downtimes'), array( +$section->add(N_('Current Downtimes'), array( 'url' => 'monitoring/list/downtimes?downtime_is_in_effect=1', 'priority' => 80 )); @@ -148,43 +148,43 @@ $section->add($this->translate('Current Downtimes'), array( /* * Overview Section */ -$section = $this->menuSection($this->translate('Overview'), array( +$section = $this->menuSection(N_('Overview'), array( 'icon' => 'sitemap', 'priority' => 30 )); -$section->add($this->translate('Tactical Overview'), array( +$section->add(N_('Tactical Overview'), array( 'url' => 'monitoring/tactical', 'priority' => 40 )); -$section->add($this->translate('Hosts'), array( +$section->add(N_('Hosts'), array( 'url' => 'monitoring/list/hosts', 'priority' => 50 )); -$section->add($this->translate('Services'), array( +$section->add(N_('Services'), array( 'url' => 'monitoring/list/services', 'priority' => 50 )); -$section->add($this->translate('Servicegroups'), array( +$section->add(N_('Servicegroups'), array( 'url' => 'monitoring/list/servicegroups', 'priority' => 60 )); -$section->add($this->translate('Hostgroups'), array( +$section->add(N_('Hostgroups'), array( 'url' => 'monitoring/list/hostgroups', 'priority' => 60 )); -$section->add($this->translate('Contacts'), array( +$section->add(N_('Contacts'), array( 'url' => 'monitoring/list/contacts', 'priority' => 70 )); -$section->add($this->translate('Contactgroups'), array( +$section->add(N_('Contactgroups'), array( 'url' => 'monitoring/list/contactgroups', 'priority' => 70 )); -$section->add($this->translate('Comments'), array( +$section->add(N_('Comments'), array( 'url' => 'monitoring/list/comments?comment_type=(comment|ack)', 'priority' => 80 )); -$section->add($this->translate('Downtimes'), array( +$section->add(N_('Downtimes'), array( 'url' => 'monitoring/list/downtimes', 'priority' => 80 )); @@ -192,23 +192,23 @@ $section->add($this->translate('Downtimes'), array( /* * History Section */ -$section = $this->menuSection($this->translate('History'), array( +$section = $this->menuSection(N_('History'), array( 'icon' => 'rewind', 'priority' => 90 )); -$section->add($this->translate('Event Grid'), array( +$section->add(N_('Event Grid'), array( 'priority' => 10, 'url' => 'monitoring/list/eventgrid' )); -$section->add($this->translate('Event Overview'), array( +$section->add(N_('Event Overview'), array( 'priority' => 20, 'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days' )); -$section->add($this->translate('Notifications'), array( +$section->add(N_('Notifications'), array( 'priority' => 30, 'url' => 'monitoring/list/notifications', )); -$section->add($this->translate('Timeline'), array( +$section->add(N_('Timeline'), array( 'priority' => 40, 'url' => 'monitoring/timeline' )); @@ -216,20 +216,20 @@ $section->add($this->translate('Timeline'), array( /* * Reporting Section */ -$section = $this->menuSection($this->translate('Reporting'), array( +$section = $this->menuSection(N_('Reporting'), array( 'icon' => 'barchart', 'priority' => 100 )); -$section->add($this->translate('Alert Summary'), array( +$section->add(N_('Alert Summary'), array( 'url' => 'monitoring/alertsummary/index' )); /* * System Section */ -$section = $this->menuSection($this->translate('System')); -$section->add($this->translate('Monitoring Health'), array( +$section = $this->menuSection(N_('System')); +$section->add(N_('Monitoring Health'), array( 'url' => 'monitoring/health/info', 'priority' => 720, 'renderer' => 'BackendAvailabilityNavigationItemRenderer' From d1fa5e8fceba6dade22cabacc327b4d36ce193fc Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 14:44:31 +0200 Subject: [PATCH 128/225] DashboardTest: Drop test testLoadPaneItemsProvidedByEnabledModules refs #5600 --- .../Icinga/Web/Widget/DashboardTest.php | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/test/php/library/Icinga/Web/Widget/DashboardTest.php b/test/php/library/Icinga/Web/Widget/DashboardTest.php index 181dec0a9..9387ed483 100644 --- a/test/php/library/Icinga/Web/Widget/DashboardTest.php +++ b/test/php/library/Icinga/Web/Widget/DashboardTest.php @@ -9,7 +9,6 @@ require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php'); use Mockery; use Icinga\Test\BaseTestCase; -use Icinga\User; use Icinga\Web\Widget\Dashboard; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Dashlet; @@ -46,23 +45,6 @@ class DashboardTest extends BaseTestCase Mockery::close(); // Necessary because some tests run in a separate process } - public function setUp() - { - $moduleMock = Mockery::mock('Icinga\Application\Modules\Module'); - $moduleMock->shouldReceive('getPaneItems')->andReturn(array( - 'test-pane' => new Pane('Test Pane') - )); - $moduleMock->shouldReceive('getName')->andReturn('test'); - - $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); - $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( - 'test-module' => $moduleMock - )); - - $bootstrapMock = $this->setupIcingaMock(); - $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); - } - public function testWhetherCreatePaneCreatesAPane() { $dashboard = new Dashboard(); @@ -126,24 +108,6 @@ class DashboardTest extends BaseTestCase ); } - /** - * @depends testWhetherCreatePaneCreatesAPane - */ - public function testLoadPaneItemsProvidedByEnabledModules() - { - $user = new User('test'); - $user->setPermissions(array('*' => '*')); - $dashboard = new Dashboard(); - $dashboard->setUser($user); - $dashboard->load(); - - $this->assertCount( - 1, - $dashboard->getPanes(), - 'Dashboard::load() could not load panes from enabled modules' - ); - } - /** * @expectedException \Icinga\Exception\ProgrammingError * @depends testWhetherCreatePaneCreatesAPane From 4c1dd2ed3641a812089fed35e663bb49650b2ed0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 15:05:43 +0200 Subject: [PATCH 129/225] NavigationController: Provide available item types externally refs #5600 --- .../controllers/NavigationController.php | 41 ++++++++++++ .../forms/Navigation/NavigationConfigForm.php | 62 +++++++++---------- 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index b1bcb3309..acad48487 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -5,6 +5,7 @@ namespace Icinga\Controllers; use Exception; use Icinga\Application\Config; +use Icinga\Application\Icinga; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfirmRemovalForm; use Icinga\Forms\Navigation\NavigationConfigForm; @@ -18,6 +19,44 @@ use Icinga\Web\Url; */ class NavigationController extends Controller { + /** + * The default item types provided by Icinga Web 2 + * + * @var array + */ + protected $defaultItemTypes; + + /** + * {@inheritdoc} + */ + public function init() + { + parent::init(); + + $this->defaultItemTypes = array( + 'menu-item' => $this->translate('Menu Entry'), + 'dashlet' => 'Dashlet' + ); + } + + /** + * Return a list of available navigation item types + * + * @return array + */ + protected function listItemTypes() + { + $types = $this->defaultItemTypes; + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + $moduleTypes = $module->getNavigationItems(); + if (! empty($moduleTypes)) { + $types = array_merge($types, $moduleTypes); + } + } + + return $types; + } + /** * Show the current user a list of his/her navigation items */ @@ -88,6 +127,7 @@ class NavigationController extends Controller { $form = new NavigationConfigForm(); $form->setRedirectUrl('navigation'); + $form->setItemTypes($this->listItemTypes()); $form->setTitle($this->translate('Create New Navigation Item')); $form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.')); $form->setUser($this->Auth()->getUser()); @@ -122,6 +162,7 @@ class NavigationController extends Controller $form = new NavigationConfigForm(); $form->setRedirectUrl('navigation'); + $form->setItemTypes($this->listItemTypes()); $form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName)); $form->setUser($this->Auth()->getUser()); $form->setShareConfig(Config::app('navigation')); diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index d758222cc..7e4583609 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -5,7 +5,6 @@ namespace Icinga\Forms\Navigation; use InvalidArgumentException; use Icinga\Application\Config; -use Icinga\Application\Icinga; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfigForm; @@ -17,13 +16,6 @@ use Icinga\Web\Form; */ class NavigationConfigForm extends ConfigForm { - /** - * The default item types provided by Icinga Web 2 - * - * @var array - */ - protected $defaultItemTypes; - /** * The secondary configuration to write * @@ -62,6 +54,13 @@ class NavigationConfigForm extends ConfigForm */ protected $shareConfig; + /** + * The available navigation item types + * + * @var array + */ + protected $itemTypes; + /** * Initialize this form */ @@ -69,11 +68,6 @@ class NavigationConfigForm extends ConfigForm { $this->setName('form_config_navigation'); $this->setSubmitLabel($this->translate('Save Changes')); - - $this->defaultItemTypes = array( - 'menu-item' => $this->translate('Menu Entry'), - 'dashlet' => 'Dashlet' - ); } /** @@ -149,6 +143,29 @@ class NavigationConfigForm extends ConfigForm return $this->shareConfig; } + /** + * Set the available navigation item types + * + * @param array $itemTypes + * + * @return $this + */ + public function setItemTypes(array $itemTypes) + { + $this->itemTypes = $itemTypes; + return $this; + } + + /** + * Return the available navigation item types + * + * @return array + */ + public function getItemTypes() + { + return $this->itemTypes ?: array(); + } + /** * Populate the form with the given navigation item's config * @@ -329,7 +346,7 @@ class NavigationConfigForm extends ConfigForm */ public function createElements(array $formData) { - $itemTypes = $this->listItemTypes(); + $itemTypes = $this->getItemTypes(); $itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes); $this->addElement( @@ -434,23 +451,6 @@ class NavigationConfigForm extends ConfigForm } } - /** - * Return a list of available item types - * - * @return array - */ - protected function listItemTypes() - { - $types = $this->defaultItemTypes; - foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $moduleName => $module) { - foreach ($module->getNavigationItems() as $type => $label) { - $types[$type] = mt($moduleName, $label); - } - } - - return $types; - } - /** * Return the navigation configuration the given item is a part of * From 47257d74ff86d5759f74071f1f86cf49c98f39d8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 15:09:56 +0200 Subject: [PATCH 130/225] user navigation overview: Display the type of a navigation item refs #5600 --- application/controllers/NavigationController.php | 1 + application/views/scripts/navigation/index.phtml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index acad48487..558a24deb 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -66,6 +66,7 @@ class NavigationController extends Controller $userConfig = $user->loadNavigationConfig(); $sharedConfig = Config::app('navigation'); + $this->view->types = $this->listItemTypes(); $this->view->items = array_merge( $sharedConfig->select()->where('owner', $user->getUsername())->fetchAll(), iterator_to_array($userConfig) diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml index 6bf60eb3c..0914821f9 100644 --- a/application/views/scripts/navigation/index.phtml +++ b/application/views/scripts/navigation/index.phtml @@ -10,6 +10,7 @@ + @@ -23,6 +24,9 @@ 'title' => sprintf($this->translate('Edit navigation item %s'), $name) ) ); ?> +
        translate('Navigation'); ?>translate('Type'); ?> translate('Remove'); ?>
        type && isset($types[$item->type]) + ? $this->escape($types[$item->type]) + : $this->escape($this->translate('Unknown')); ?> qlink( '', 'navigation/remove', From 1e39ec668a4e2f1ff531418a27a6859599610fdf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 15:31:19 +0200 Subject: [PATCH 131/225] monitoring: Disable custom host- and service-notes refs #5600 --- modules/monitoring/configuration.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 2398ed337..c69ebc97b 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -90,8 +90,9 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv */ $this->provideNavigationItem('host-action', $this->translate('Host Action')); $this->provideNavigationItem('service-action', $this->translate('Service Action')); -$this->provideNavigationItem('host-note', $this->translate('Host Note')); -$this->provideNavigationItem('service-note', $this->translate('Service Note')); +// Notes are disabled as we're not sure whether to really make a difference between actions and notes +//$this->provideNavigationItem('host-note', $this->translate('Host Note')); +//$this->provideNavigationItem('service-note', $this->translate('Service Note')); /* * Problems Section From 54d08f99e85349eb5ee630d318560342e086057a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 15:31:41 +0200 Subject: [PATCH 132/225] DashboardPane: Order dashlets alphabetically refs #5600 --- .../Icinga/Web/Navigation/DashboardPane.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php index 200287668..e598a0508 100644 --- a/library/Icinga/Web/Navigation/DashboardPane.php +++ b/library/Icinga/Web/Navigation/DashboardPane.php @@ -33,11 +33,21 @@ class DashboardPane extends NavigationItem /** * Return this pane's dashlets * + * @param bool $order Whether to order the dashlets first + * * @return array */ - public function getDashlets() + public function getDashlets($order = true) { - return $this->dashlets ?: array(); + if ($this->dashlets === null) { + return array(); + } + + if ($order) { + ksort($this->dashlets); + } + + return $this->dashlets; } /** @@ -56,8 +66,8 @@ class DashboardPane extends NavigationItem parent::merge($item); $this->setDashlets(array_merge( - $this->getDashlets(), - $item->getDashlets() + $this->getDashlets(false), + $item->getDashlets(false) )); } } From 99502a77e025e23489e816aee09409286265fe32 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 15:37:56 +0200 Subject: [PATCH 133/225] shared navigation overview: Display the type of a navigation item refs #5600 --- application/controllers/NavigationController.php | 1 + application/views/scripts/navigation/shared.phtml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 558a24deb..8c5ac019e 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -89,6 +89,7 @@ class NavigationController extends Controller { $this->assertPermission('config/application/navigation'); $this->view->items = Config::app('navigation'); + $this->view->types = $this->listItemTypes(); $removeForm = new Form(); $removeForm->setUidDisabled(); diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index c7bcb7d0e..d28378653 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -10,6 +10,7 @@ + @@ -24,6 +25,9 @@ 'title' => sprintf($this->translate('Edit shared navigation item %s'), $name) ) ); ?> + From 0cff2ca5451dd752dec8a860090749034b3cf2dc Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 16:24:29 +0200 Subject: [PATCH 134/225] RoleForm: Provide share restrictions for users and groups refs #5600 --- application/forms/Security/RoleForm.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 16fabf78c..9ebfc2c91 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -28,7 +28,7 @@ class RoleForm extends ConfigForm * * @var array */ - protected $providedRestrictions = array(); + protected $providedRestrictions; /** * {@inheritdoc} @@ -70,9 +70,23 @@ class RoleForm extends ConfigForm 'config/modules' => 'config/modules' */ ); - - + $helper = new Zend_Form_Element('bogus'); + $this->providedRestrictions = array( + $helper->filterName('application/share/users') => array( + 'name' => 'application/share/users', + 'description' => $this->translate( + 'Restrict which users this role can share items and information with' + ) + ), + $helper->filterName('application/share/groups') => array( + 'name' => 'application/share/groups', + 'description' => $this->translate( + 'Restrict which groups this role can share items and information with' + ) + ) + ); + $mm = Icinga::app()->getModuleManager(); foreach ($mm->listInstalledModules() as $moduleName) { $modulePermission = $mm::MODULE_PERMISSION_NS . $moduleName; From 4385d74e16e5d60067dd15608d7f035ed52cebe7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 16:25:17 +0200 Subject: [PATCH 135/225] NavigationConfigForm: Apply share restrictions for users and groups refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 7e4583609..c359eda6d 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -5,6 +5,7 @@ namespace Icinga\Forms\Navigation; use InvalidArgumentException; use Icinga\Application\Config; +use Icinga\Authentication\Auth; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfigForm; @@ -427,6 +428,63 @@ class NavigationConfigForm extends ConfigForm } } + /** + * {@inheritdoc} + */ + public function isValid($formData) + { + if (! parent::isValid($formData)) { + return false; + } + + $valid = true; + if (isset($formData['users']) && $formData['users']) { + $parsedUserRestrictions = array(); + foreach (Auth::getInstance()->getRestrictions('application/share/users') as $userRestriction) { + $parsedUserRestrictions[] = array_map('trim', explode(',', $userRestriction)); + } + + if (! empty($parsedUserRestrictions)) { + $desiredUsers = array_map('trim', explode(',', $formData['users'])); + array_unshift($parsedUserRestrictions, $desiredUsers); + $forbiddenUsers = call_user_func_array('array_diff', $parsedUserRestrictions); + if (! empty($forbiddenUsers)) { + $valid = false; + $this->getElement('users')->addError( + $this->translate(sprintf( + 'You are not permitted to share this navigation item with the following users: %s', + implode(', ', $forbiddenUsers) + )) + ); + } + } + } + + if (isset($formData['groups']) && $formData['groups']) { + $parsedGroupRestrictions = array(); + foreach (Auth::getInstance()->getRestrictions('application/share/groups') as $groupRestriction) { + $parsedGroupRestrictions[] = array_map('trim', explode(',', $groupRestriction)); + } + + if (! empty($parsedGroupRestrictions)) { + $desiredGroups = array_map('trim', explode(',', $formData['groups'])); + array_unshift($parsedGroupRestrictions, $desiredGroups); + $forbiddenGroups = call_user_func_array('array_diff', $parsedGroupRestrictions); + if (! empty($forbiddenGroups)) { + $valid = false; + $this->getElement('groups')->addError( + $this->translate(sprintf( + 'You are not permitted to share this navigation item with the following groups: %s', + implode(', ', $forbiddenGroups) + )) + ); + } + } + } + + return $valid; + } + /** * {@inheritdoc} */ From 0e60b817291ea401ddc7fc8b4e44387ca8a70cb2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 16 Sep 2015 16:29:30 +0200 Subject: [PATCH 136/225] monitoring: Give the "Stats" dashboard pane a priority of 99, instead of 100 refs #5600 --- modules/monitoring/configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index c69ebc97b..2d54137d4 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -348,7 +348,7 @@ $dashboard->add( /* * Stats */ -$dashboard = $this->dashboard(N_('Stats'), array('priority' => 100)); +$dashboard = $this->dashboard(N_('Stats'), array('priority' => 99)); $dashboard->add( N_('Check Stats'), 'monitoring/health/stats' From 6a52de8a44de0cad6a61eff4f77c2ca4522c55ea Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 08:24:23 +0200 Subject: [PATCH 137/225] NavigationConfigForm: Load form classes dynamically refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index c359eda6d..217da8a60 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -5,11 +5,14 @@ namespace Icinga\Forms\Navigation; use InvalidArgumentException; use Icinga\Application\Config; +use Icinga\Application\Logger; +use Icinga\Application\Icinga; use Icinga\Authentication\Auth; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfigForm; use Icinga\User; +use Icinga\Util\String; use Icinga\Web\Form; /** @@ -17,6 +20,13 @@ use Icinga\Web\Form; */ class NavigationConfigForm extends ConfigForm { + /** + * The class namespace where to locate navigation type forms + * + * @var string + */ + const FORM_NS = 'Forms\\Navigation'; + /** * The secondary configuration to write * @@ -551,7 +561,41 @@ class NavigationConfigForm extends ConfigForm */ protected function getItemForm($type) { - // TODO: Load form classes dynamically - return new NavigationItemForm(); + $className = String::cname($type, '-') . 'Form'; + + $form = null; + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + $classPath = 'Icinga\\Module\\' + . ucfirst($module->getName()) + . '\\' + . static::FORM_NS + . '\\' + . $className; + if (class_exists($classPath)) { + $form = new $classPath(); + break; + } + } + + if ($form === null) { + $classPath = 'Icinga\\' . static::FORM_NS . '\\' . $className; + if (class_exists($classPath)) { + $form = new $classPath(); + } + } + + if ($form === null) { + Logger::debug( + 'Failed to find custom navigation item form %s for item %s. Using form NavigationItemForm now', + $className, + $type + ); + + $form = new NavigationItemForm(); + } elseif (! $form instanceof Form) { + throw new ProgrammingError('Class %s must inherit from Form', $classPath); + } + + return $form; } } From 84f733b17743ac6093d332b06ce070fef19e45ad Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 08:25:17 +0200 Subject: [PATCH 138/225] DashboardPane: Rename param $order to $ordered in method getDashlets refs #5600 --- library/Icinga/Web/Navigation/DashboardPane.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php index e598a0508..3bf6d0992 100644 --- a/library/Icinga/Web/Navigation/DashboardPane.php +++ b/library/Icinga/Web/Navigation/DashboardPane.php @@ -33,17 +33,17 @@ class DashboardPane extends NavigationItem /** * Return this pane's dashlets * - * @param bool $order Whether to order the dashlets first + * @param bool $ordered Whether to order the dashlets first * * @return array */ - public function getDashlets($order = true) + public function getDashlets($ordered = true) { if ($this->dashlets === null) { return array(); } - if ($order) { + if ($ordered) { ksort($this->dashlets); } From 2d9f20f2c516d8797c9191dca98a60659ff2e222 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 08:37:41 +0200 Subject: [PATCH 139/225] Introduce form class DashletForm refs #5600 --- application/forms/Navigation/DashletForm.php | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 application/forms/Navigation/DashletForm.php diff --git a/application/forms/Navigation/DashletForm.php b/application/forms/Navigation/DashletForm.php new file mode 100644 index 000000000..dc9a02803 --- /dev/null +++ b/application/forms/Navigation/DashletForm.php @@ -0,0 +1,34 @@ +addElement( + 'text', + 'pane', + array( + 'required' => true, + 'label' => $this->translate('Pane'), + 'description' => $this->translate('The name of the dashboard pane in which to display this dashlet') + ) + ); + $this->addElement( + 'text', + 'url', + array( + 'required' => true, + 'label' => $this->translate('Url'), + 'description' => $this->translate('The url to load in the dashlet') + ) + ); + } +} From b1ee12f72130e40192a9df25f749162c6d6595e8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 09:11:17 +0200 Subject: [PATCH 140/225] NavigationItem: Allow to dynamically decide whether to render an item refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 43 +++++++++++++++++++ .../Renderer/NavigationRenderer.php | 8 ++-- .../Renderer/RecursiveNavigationRenderer.php | 10 +++-- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 01888c41d..a72390e48 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -108,6 +108,13 @@ class NavigationItem implements IteratorAggregate */ protected $renderer; + /** + * Whether to render this item + * + * @var bool + */ + protected $render; + /** * Create a new NavigationItem * @@ -117,6 +124,7 @@ class NavigationItem implements IteratorAggregate public function __construct($name, array $properties = null) { $this->setName($name); + $this->render = true; $this->priority = 100; $this->children = new Navigation(); @@ -689,6 +697,41 @@ class NavigationItem implements IteratorAggregate return $this->renderer; } + /** + * Set whether this item should be rendered + * + * @param bool $state + * + * @return $this + */ + public function setRender($state = true) + { + $this->render = (bool) $state; + return $this; + } + + /** + * Return whether this item should be rendered + * + * @return bool + */ + public function getRender() + { + return $this->render; + } + + /** + * Return whether this item should be rendered + * + * Alias for NavigationItem::getRender(). + * + * @return bool + */ + public function shouldRender() + { + return $this->getRender(); + } + /** * Return this item rendered to HTML * diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index 219df6e2c..4818f402e 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -347,9 +347,11 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa { foreach ($this as $item) { /** @var NavigationItem $item */ - $this->content[] = $this->beginItemMarkup($item); - $this->content[] = $item->render(); - $this->content[] = $this->endItemMarkup(); + if ($item->shouldRender()) { + $this->content[] = $this->beginItemMarkup($item); + $this->content[] = $item->render(); + $this->content[] = $this->endItemMarkup(); + } } return join("\n", $this->content); diff --git a/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php index 70cf071ab..83b2207bb 100644 --- a/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php @@ -129,10 +129,12 @@ class RecursiveNavigationRenderer extends RecursiveIteratorIterator implements N { foreach ($this as $item) { /** @var NavigationItem $item */ - $this->content[] = $this->getInnerIterator()->beginItemMarkup($item); - $this->content[] = $item->render(); - if (! $item->hasChildren()) { - $this->content[] = $this->getInnerIterator()->endItemMarkup(); + if ($item->shouldRender()) { + $this->content[] = $this->getInnerIterator()->beginItemMarkup($item); + $this->content[] = $item->render(); + if (! $item->hasChildren()) { + $this->content[] = $this->getInnerIterator()->endItemMarkup(); + } } } From 25307fc1a057a21e06778ae861c59d28cb50d13a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 09:21:33 +0200 Subject: [PATCH 141/225] Introduce form class HostActionForm refs #5600 --- .../forms/Navigation/HostActionForm.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 modules/monitoring/application/forms/Navigation/HostActionForm.php diff --git a/modules/monitoring/application/forms/Navigation/HostActionForm.php b/modules/monitoring/application/forms/Navigation/HostActionForm.php new file mode 100644 index 000000000..726d740d0 --- /dev/null +++ b/modules/monitoring/application/forms/Navigation/HostActionForm.php @@ -0,0 +1,30 @@ +addElement( + 'text', + 'filter', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Filter'), + 'description' => $this->translate( + 'Display this action only for hosts matching this filter. Leave' + . ' blank if you want this action being displayed for all hosts' + ) + ) + ); + } +} From ade0c605a9d291a6ad6d33c2a21ac2ca1e5ff083 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 09:21:43 +0200 Subject: [PATCH 142/225] Introduce form class ServiceActionForm refs #5600 --- .../forms/Navigation/ServiceActionForm.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 modules/monitoring/application/forms/Navigation/ServiceActionForm.php diff --git a/modules/monitoring/application/forms/Navigation/ServiceActionForm.php b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php new file mode 100644 index 000000000..6be89b0a3 --- /dev/null +++ b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php @@ -0,0 +1,30 @@ +addElement( + 'text', + 'filter', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Filter'), + 'description' => $this->translate( + 'Display this action only for services matching this filter. Leave' + . ' blank if you want this action being displayed for all services' + ) + ) + ); + } +} From f4032988bf0a2bf4958cbcac98d81ee841b2f6b6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 10:40:00 +0200 Subject: [PATCH 143/225] MonitoredObject: Add method matches() refs #5600 --- .../Monitoring/Object/MonitoredObject.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 0896cd266..76e666d89 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -5,9 +5,11 @@ namespace Icinga\Module\Monitoring\Object; use InvalidArgumentException; use Icinga\Application\Config; +use Icinga\Application\Logger; use Icinga\Data\Filter\Filter; use Icinga\Data\Filterable; use Icinga\Exception\InvalidPropertyException; +use Icinga\Exception\ProgrammingError; use Icinga\Module\Monitoring\Backend\MonitoringBackend; use Icinga\Web\UrlParams; @@ -208,6 +210,40 @@ abstract class MonitoredObject implements Filterable // Left out on purpose. Interface is deprecated. } + /** + * Return whether this object matches the given filter + * + * @param Filter $filter + * + * @return bool + * + * @throws ProgrammingError In case the object cannot be found + */ + public function matches(Filter $filter) + { + if ($this->properties === null && $this->fetch() === false) { + throw new ProgrammingError( + 'Unable to apply filter. Object %s of type %s not found.', + $this->getName(), + $this->getType() + ); + } + + $row = clone $this->properties; + + if ($this->customvars === null) { + $this->fetchCustomvars(); + } + + foreach ($this->customvars as $name => $value) { + if (! is_object($value)) { + $row->{'_' . $this->getType() . '_' . strtolower(str_replace(' ', '_', $name))} = $value; + } + } + + return $filter->matches($row); + } + /** * Require the object's type to be one of the given types * From bf4e492fa1c5b102a3cc97edb32d94d2097db1b8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 11:00:40 +0200 Subject: [PATCH 144/225] Action: Accept and apply option "filter" refs #5600 --- .../Icinga/Web/Navigation/NavigationItem.php | 5 ++- .../Monitoring/Web/Navigation/Action.php | 44 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index a72390e48..c1e8b46b0 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -124,7 +124,6 @@ class NavigationItem implements IteratorAggregate public function __construct($name, array $properties = null) { $this->setName($name); - $this->render = true; $this->priority = 100; $this->children = new Navigation(); @@ -717,6 +716,10 @@ class NavigationItem implements IteratorAggregate */ public function getRender() { + if ($this->render === null) { + return true; + } + return $this->render; } diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php index 59de17c29..505229abb 100644 --- a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php +++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Monitoring\Web\Navigation; +use Icinga\Data\Filter\Filter; use Icinga\Web\Navigation\NavigationItem; use Icinga\Module\Monitoring\Object\Macro; use Icinga\Module\Monitoring\Object\MonitoredObject; @@ -26,6 +27,13 @@ class Action extends NavigationItem */ protected $object; + /** + * The filter to use when being asked whether to render this action + * + * @var string + */ + protected $filter; + /** * Set this action's object * @@ -49,6 +57,29 @@ class Action extends NavigationItem return $this->object; } + /** + * Set the filter to use when being asked whether to render this action + * + * @param string $filter + * + * @return $this + */ + public function setFilter($filter) + { + $this->filter = $filter; + return $this; + } + + /** + * Return the filter to use when being asked whether to render this action + * + * @return string + */ + public function getFilter() + { + return $this->filter; + } + /** * {@inheritdoc} */ @@ -62,4 +93,17 @@ class Action extends NavigationItem return $url; } + + /** + * {@inheritdoc} + */ + public function getRender() + { + if ($this->render === null) { + $filter = $this->getFilter(); + $this->render = $filter ? $this->getObject()->matches(Filter::fromQueryString($filter)) : true; + } + + return $this->render; + } } From 4f5a5c83bc7246964540ca7e929d0d0e686a18cb Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 11:01:06 +0200 Subject: [PATCH 145/225] Navigation: Add method hasRenderableItems() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 16 ++++++++++++++++ .../views/scripts/show/components/actions.phtml | 2 +- .../views/scripts/show/components/notes.phtml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 8f1969a7f..0d22494af 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -263,6 +263,22 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate return empty($this->items); } + /** + * Return whether this navigation has any renderable items + * + * @return bool + */ + public function hasRenderableItems() + { + foreach ($this->getItems() as $item) { + if ($item->shouldRender()) { + return true; + } + } + + return false; + } + /** * Return this navigation's layout * diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml index c0d40c4bc..2e55db68c 100644 --- a/modules/monitoring/application/views/scripts/show/components/actions.phtml +++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml @@ -27,7 +27,7 @@ if (isset($this->actions)) { } } -if ($navigation->isEmpty()) { +if ($navigation->isEmpty() || !$navigation->hasRenderableItems()) { return; } diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml index 64f4a491d..1b3dafc52 100644 --- a/modules/monitoring/application/views/scripts/show/components/notes.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml @@ -32,7 +32,7 @@ if (! empty($links)) { } } -if ($navigation->isEmpty()) { +if ($navigation->isEmpty() || !$navigation->hasRenderableItems()) { return; } ?> From 83bde3aa81cadbef1698409cad3b8c21de71dd6d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 11:23:47 +0200 Subject: [PATCH 146/225] Form: Rename property $parent to $_parent Zend.. I love you.. --- library/Icinga/Web/Form.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index f3a2c8c80..aa4ab2cbf 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -72,7 +72,7 @@ class Form extends Zend_Form * * @var Form */ - protected $parent; + protected $_parent; /** * Whether the form is an API target @@ -261,7 +261,7 @@ class Form extends Zend_Form */ public function setParent(Form $form) { - $this->parent = $form; + $this->_parent = $form; return $this; } @@ -272,7 +272,7 @@ class Form extends Zend_Form */ public function getParent() { - return $this->parent; + return $this->_parent; } /** From 0ec361034928e702045692a7d97aa98e542ce1f9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:40:40 +0200 Subject: [PATCH 147/225] Web: Properly load shared menu items refs #5600 --- library/Icinga/Application/Web.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 765170b84..538afb029 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -182,7 +182,6 @@ class Web extends EmbeddedWeb $config = Config::app('navigation')->getConfigObject(); $config->setKeyColumn('name'); - $navigation = new Navigation(); if ($type === 'dashboard-pane') { $panes = array(); foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) { @@ -192,6 +191,7 @@ class Web extends EmbeddedWeb } } + $navigation = new Navigation(); foreach ($panes as $paneName => $dashlets) { $navigation->addItem( $paneName, @@ -202,11 +202,14 @@ class Web extends EmbeddedWeb ); } } else { + $items = array(); foreach ($config->select()->where('type', $type) as $name => $typeConfig) { if ($this->hasAccessToSharedNavigationItem($typeConfig)) { - $navigation->addItem($name, $typeConfig->toArray()); + $items[$name] = $typeConfig; } } + + $navigation = Navigation::fromConfig($items); } return $navigation; From bb4f4e90953f1a59dfeca481443e1ff715d6729b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:40:58 +0200 Subject: [PATCH 148/225] User: Properly load menu items refs #5600 --- library/Icinga/User.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/Icinga/User.php b/library/Icinga/User.php index 4af29b35f..114e59755 100644 --- a/library/Icinga/User.php +++ b/library/Icinga/User.php @@ -507,7 +507,6 @@ class User $config = $this->loadNavigationConfig(); $config->getConfigObject()->setKeyColumn('name'); - $navigation = new Navigation(); if ($type === 'dashboard-pane') { $panes = array(); foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) { @@ -515,6 +514,7 @@ class User $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; } + $navigation = new Navigation(); foreach ($panes as $paneName => $dashlets) { $navigation->addItem( $paneName, @@ -525,9 +525,7 @@ class User ); } } else { - foreach ($config->select()->where('type', $type) as $name => $typeConfig) { - $navigation->addItem($name, $typeConfig->toArray()); - } + $navigation = Navigation::fromConfig($config->select()->where('type', $type)); } return $navigation; From b6fe6ffd6bf17b190102ec55dcfd10ea29471404 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:41:28 +0200 Subject: [PATCH 149/225] Navigation: Relax type check in method fromConfig() refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 0d22494af..6c1b931b9 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -9,7 +9,7 @@ use Exception; use Countable; use InvalidArgumentException; use IteratorAggregate; -use Icinga\Application\Config; +use Traversable; use Icinga\Application\Icinga; use Icinga\Application\Logger; use Icinga\Authentication\Auth; @@ -454,14 +454,22 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate /** * Create and return a new set of navigation items for the given configuration * - * @param Config $config + * Note that this is supposed to be utilized for one dimensional structures + * only. Multi dimensional structures can be processed by fromArray(). + * + * @param Traversable|array $config * * @return Navigation * - * @throws ConfigurationError In case a referenced parent does not exist + * @throws InvalidArgumentException In case the given configuration is invalid + * @throws ConfigurationError In case a referenced parent does not exist */ - public static function fromConfig(Config $config) + public static function fromConfig($config) { + if (! is_array($config) && !$config instanceof Traversable) { + throw new InvalidArgumentException('Argument $config must be an array or a instance of Traversable'); + } + $flattened = $topLevel = array(); foreach ($config as $sectionName => $sectionConfig) { $parentName = $sectionConfig->parent; From f8d53c1d073ee1182f1764cef575546edb8ef573 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:42:25 +0200 Subject: [PATCH 150/225] NavigationConfigForm: Sub-forms may require a parent being set refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 217da8a60..78be4d4c6 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -423,7 +423,9 @@ class NavigationConfigForm extends ConfigForm ) ); - $this->addSubForm($this->getItemForm($itemType)->create($formData), 'item_form'); + $itemForm = $this->getItemForm($itemType); + $this->addSubForm($itemForm, 'item_form'); + $itemForm->create($formData); // Requires a parent which gets set by addSubForm() } /** From 6d743732760b773a5126d0bfd97c7b048b10c528 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:45:47 +0200 Subject: [PATCH 151/225] NavigationConfigForm: Add method listAvailableParents() refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 78be4d4c6..a5b01e490 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -177,6 +177,46 @@ class NavigationConfigForm extends ConfigForm return $this->itemTypes ?: array(); } + /** + * Return a list of available parent items for the given type of navigation item + * + * @return array + */ + public function listAvailableParents($type) + { + $shared = false; + if (($checkbox = $this->getElement('shared')) !== null) { + if ($checkbox->isChecked()) { + $shared = true; + } else { + $requestData = $this->getRequestData(); + $shared = isset($requestData['shared']) && $requestData['shared']; + } + } + + $names = array(); + if ($shared) { + foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { + if ( + $sectionName !== $this->itemToLoad + && $sectionConfig->type === $type + && $sectionConfig->owner === $this->getUser()->getUsername() + ) { + $names[] = $sectionName; + } + } + } else { + foreach ($this->getUserConfig() as $sectionName => $sectionConfig) { + if ($sectionName !== $this->itemToLoad && $sectionConfig->type === $type) { + $names[] = $sectionName; + } + } + } + + // TODO: Ensure that it's not possible to produce circular references + return $names; + } + /** * Populate the form with the given navigation item's config * From 7b5d414c7f07fd277d051917f485bc8eb06c61bd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 13:46:08 +0200 Subject: [PATCH 152/225] Introduce form class MenuItemForm refs #5600 --- application/forms/Navigation/MenuItemForm.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 application/forms/Navigation/MenuItemForm.php diff --git a/application/forms/Navigation/MenuItemForm.php b/application/forms/Navigation/MenuItemForm.php new file mode 100644 index 000000000..9f17495a3 --- /dev/null +++ b/application/forms/Navigation/MenuItemForm.php @@ -0,0 +1,32 @@ +getParent()->listAvailableParents('menu-item'); + $this->addElement( + 'select', + 'parent', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Parent'), + 'description' => $this->translate( + 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry' + ), + 'multiOptions' => array_merge( + array('' => $this->translate('None', 'No parent for a navigation item')), + empty($availableParents) ? array() : array_combine($availableParents, $availableParents) + ) + ) + ); + + parent::createElements($formData); + } +} From d4a91983106190afc161a8d7e42854da8ede570a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 14:42:02 +0200 Subject: [PATCH 153/225] Navigation: Only fail if there's really no chance to create the requested hierarchy refs #5600 --- library/Icinga/Web/Navigation/Navigation.php | 41 +++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php index 6c1b931b9..9cd3423a3 100644 --- a/library/Icinga/Web/Navigation/Navigation.php +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -470,7 +470,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate throw new InvalidArgumentException('Argument $config must be an array or a instance of Traversable'); } - $flattened = $topLevel = array(); + $flattened = $orphans = $topLevel = array(); foreach ($config as $sectionName => $sectionConfig) { $parentName = $sectionConfig->parent; unset($sectionConfig->parent); @@ -482,17 +482,40 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate $flattened[$parentName]['children'][$sectionName] = $sectionConfig->toArray(); $flattened[$sectionName] = & $flattened[$parentName]['children'][$sectionName]; } else { - throw new ConfigurationError( - t( - 'Failed to add navigation item "%s". Parent "%s" not found. Make' - . ' sure that the parent is defined prior to its child(s).' - ), - $sectionName, - $parentName - ); + $orphans[$parentName][$sectionName] = $sectionConfig->toArray(); + $flattened[$sectionName] = & $orphans[$parentName][$sectionName]; } } + do { + $match = false; + foreach ($orphans as $parentName => $children) { + if (isset($flattened[$parentName])) { + if (isset($flattened[$parentName]['children'])) { + $flattened[$parentName]['children'] = array_merge( + $flattened[$parentName]['children'], + $children + ); + } else { + $flattened[$parentName]['children'] = $children; + } + + unset($orphans[$parentName]); + $match = true; + } + } + } while ($match && !empty($orphans)); + + if (! empty($orphans)) { + throw new ConfigurationError( + t( + 'Failed to fully parse navigation configuration. Ensure that' + . ' all referenced parents are existing navigation items: %s' + ), + join(', ', array_keys($orphans)) + ); + } + return static::fromArray($topLevel); } From 6e2d7dca9b1114a692796a9e6b782945c3b85608 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 15:17:46 +0200 Subject: [PATCH 154/225] shared navigation overview: Add sort control refs #5600 --- application/controllers/NavigationController.php | 16 ++++++++++++++-- .../views/scripts/navigation/shared.phtml | 6 +++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 8c5ac019e..da33a5291 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -88,8 +88,9 @@ class NavigationController extends Controller public function sharedAction() { $this->assertPermission('config/application/navigation'); - $this->view->items = Config::app('navigation'); - $this->view->types = $this->listItemTypes(); + $config = Config::app('navigation'); + $config->getConfigObject()->setKeyColumn('name'); + $query = $config->select(); $removeForm = new Form(); $removeForm->setUidDisabled(); @@ -110,7 +111,10 @@ class NavigationController extends Controller 'label' => $this->view->icon('trash'), 'title' => $this->translate('Unshare this navigation item') )); + $this->view->removeForm = $removeForm; + $this->view->types = $this->listItemTypes(); + $this->view->items = $query; $this->getTabs()->add( 'navigation/shared', @@ -120,6 +124,14 @@ class NavigationController extends Controller 'url' => 'navigation/shared' ) )->activate('navigation/shared'); + $this->setupSortControl( + array( + 'name' => $this->translate('Shared Navigation'), + 'type' => $this->translate('Type'), + 'owner' => $this->translate('Owner') + ), + $query + ); } /** diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index d28378653..8d52fd7b5 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -1,10 +1,14 @@ compact): ?>
        tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
        -isEmpty()): ?> + translate('There are currently no navigation items being shared'); ?>
        translate('Shared Navigation'); ?>translate('Type'); ?> translate('Owner'); ?> translate('Unshare'); ?>
        type && isset($types[$item->type]) + ? $this->escape($types[$item->type]) + : $this->escape($this->translate('Unknown')); ?> escape($item->owner); ?> setDefault('name', $name); ?>
        From 996224f54ad9fa79e81c63c6abb074c0bd030291 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 17 Sep 2015 15:53:12 +0200 Subject: [PATCH 155/225] NavigationConfigForm: Do not allow to configure circular parent child relations refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index a5b01e490..f2bb0be70 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -194,6 +194,8 @@ class NavigationConfigForm extends ConfigForm } } + $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array(); + $names = array(); if ($shared) { foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { @@ -201,22 +203,51 @@ class NavigationConfigForm extends ConfigForm $sectionName !== $this->itemToLoad && $sectionConfig->type === $type && $sectionConfig->owner === $this->getUser()->getUsername() + && !in_array($sectionName, $children, true) ) { $names[] = $sectionName; } } } else { foreach ($this->getUserConfig() as $sectionName => $sectionConfig) { - if ($sectionName !== $this->itemToLoad && $sectionConfig->type === $type) { + if ( + $sectionName !== $this->itemToLoad + && $sectionConfig->type === $type + && !in_array($sectionName, $children, true) + ) { $names[] = $sectionName; } } } - // TODO: Ensure that it's not possible to produce circular references return $names; } + /** + * Recursively return all children of the given navigation item + * + * @param string $name + * + * @return array + */ + protected function getFlattenedChildren($name) + { + $config = $this->getConfigForItem($name); + if ($config === null) { + return array(); + } + + $children = array(); + foreach ($config as $sectionName => $sectionConfig) { + if ($sectionConfig->parent === $name) { + $children[] = $sectionName; + $children = array_merge($children, $this->getFlattenedChildren($sectionName)); + } + } + + return $children; + } + /** * Populate the form with the given navigation item's config * From 2cf52df0a8aa71d6e8591c846a5f6222f2afb0df Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 08:53:05 +0200 Subject: [PATCH 156/225] user navigation overview: Add sort control refs #5600 --- .../controllers/NavigationController.php | 22 ++++++++++++++----- .../views/scripts/navigation/index.phtml | 8 +++++++ .../views/scripts/navigation/shared.phtml | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index da33a5291..9aad0e7e6 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -7,6 +7,7 @@ use Exception; use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Exception\NotFoundError; +use Icinga\Data\DataArray\ArrayDatasource; use Icinga\Forms\ConfirmRemovalForm; use Icinga\Forms\Navigation\NavigationConfigForm; use Icinga\Web\Controller; @@ -63,14 +64,16 @@ class NavigationController extends Controller public function indexAction() { $user = $this->Auth()->getUser(); - $userConfig = $user->loadNavigationConfig(); - $sharedConfig = Config::app('navigation'); + + $ds = new ArrayDatasource(array_merge( + Config::app('navigation')->select()->where('owner', $user->getUsername())->fetchAll(), + iterator_to_array($user->loadNavigationConfig()) + )); + $ds->setKeyColumn('name'); + $query = $ds->select(); $this->view->types = $this->listItemTypes(); - $this->view->items = array_merge( - $sharedConfig->select()->where('owner', $user->getUsername())->fetchAll(), - iterator_to_array($userConfig) - ); + $this->view->items = $query; $this->getTabs()->add( 'navigation', @@ -80,6 +83,13 @@ class NavigationController extends Controller 'url' => 'navigation' ) )->activate('navigation'); + $this->setupSortControl( + array( + 'name' => $this->translate('Shared Navigation'), + 'type' => $this->translate('Type') + ), + $query + ); } /** diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml index 0914821f9..796e3b7c1 100644 --- a/application/views/scripts/navigation/index.phtml +++ b/application/views/scripts/navigation/index.phtml @@ -1,12 +1,19 @@ compact): ?>
        tabs; ?> + sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?>
        icon('plus'); ?> translate('Create A New Navigation Item'); ?> + +

        translate('You did not create any navigation item yet'); ?>

        +
        @@ -40,4 +47,5 @@
        translate('Navigation'); ?>
        + \ No newline at end of file diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index 8d52fd7b5..0d7555020 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -9,7 +9,7 @@
        - translate('There are currently no navigation items being shared'); ?> +

        translate('There are currently no navigation items being shared'); ?>

        From b39eddf69d2a57902030e7cd74c52c946287ddc0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 08:58:57 +0200 Subject: [PATCH 157/225] navigation overviews: Use "type" as default sort column instead of "name" As long as we're not splitting the items by type in different views, this sort column is probably more commonly used.. refs #5600 --- application/controllers/NavigationController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 9aad0e7e6..db02adb89 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -85,8 +85,8 @@ class NavigationController extends Controller )->activate('navigation'); $this->setupSortControl( array( - 'name' => $this->translate('Shared Navigation'), - 'type' => $this->translate('Type') + 'type' => $this->translate('Type'), + 'name' => $this->translate('Shared Navigation') ), $query ); @@ -136,9 +136,9 @@ class NavigationController extends Controller )->activate('navigation/shared'); $this->setupSortControl( array( - 'name' => $this->translate('Shared Navigation'), 'type' => $this->translate('Type'), - 'owner' => $this->translate('Owner') + 'owner' => $this->translate('Owner'), + 'name' => $this->translate('Shared Navigation') ), $query ); From c657812b9c297528e06b1a902776de9b3489e628 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 09:43:40 +0200 Subject: [PATCH 158/225] NavigationConfigForm: Require NavigationItemForm as base class refs #5600 --- application/forms/Navigation/DashletForm.php | 4 +--- application/forms/Navigation/NavigationConfigForm.php | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/application/forms/Navigation/DashletForm.php b/application/forms/Navigation/DashletForm.php index dc9a02803..c1e6f66ec 100644 --- a/application/forms/Navigation/DashletForm.php +++ b/application/forms/Navigation/DashletForm.php @@ -3,9 +3,7 @@ namespace Icinga\Forms\Navigation; -use Icinga\Web\Form; - -class DashletForm extends Form +class DashletForm extends NavigationItemForm { /** * {@inheritdoc} diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index f2bb0be70..4a013901e 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -10,6 +10,7 @@ use Icinga\Application\Icinga; use Icinga\Authentication\Auth; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; +use Icinga\Exception\ProgrammingError; use Icinga\Forms\ConfigForm; use Icinga\User; use Icinga\Util\String; @@ -665,8 +666,8 @@ class NavigationConfigForm extends ConfigForm ); $form = new NavigationItemForm(); - } elseif (! $form instanceof Form) { - throw new ProgrammingError('Class %s must inherit from Form', $classPath); + } elseif (! $form instanceof NavigationItemForm) { + throw new ProgrammingError('Class %s must inherit from NavigationItemForm', $classPath); } return $form; From 91601de39823e380844138fe94c1591ad83ad9b4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 09:48:22 +0200 Subject: [PATCH 159/225] NavigationConfigForm: Ask the item form whether a parent is required refs #5600 --- application/forms/Navigation/MenuItemForm.php | 25 +++++----------- .../forms/Navigation/NavigationConfigForm.php | 30 +++++++++++++++++-- .../forms/Navigation/NavigationItemForm.php | 17 +++++++++++ 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/application/forms/Navigation/MenuItemForm.php b/application/forms/Navigation/MenuItemForm.php index 9f17495a3..3cc296bfe 100644 --- a/application/forms/Navigation/MenuItemForm.php +++ b/application/forms/Navigation/MenuItemForm.php @@ -5,28 +5,19 @@ namespace Icinga\Forms\Navigation; class MenuItemForm extends NavigationItemForm { + /** + * {@inheritdoc} + */ + protected $requiresParentSelection = true; + /** * {@inheritdoc} */ public function createElements(array $formData) { - $availableParents = $this->getParent()->listAvailableParents('menu-item'); - $this->addElement( - 'select', - 'parent', - array( - 'allowEmpty' => true, - 'label' => $this->translate('Parent'), - 'description' => $this->translate( - 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry' - ), - 'multiOptions' => array_merge( - array('' => $this->translate('None', 'No parent for a navigation item')), - empty($availableParents) ? array() : array_combine($availableParents, $availableParents) - ) - ) - ); - parent::createElements($formData); + $this->getParent()->getElement('parent')->setDescription($this->translate( + 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry' + )); } } diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 4a013901e..33dab2bd4 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -431,6 +431,7 @@ class NavigationConfigForm extends ConfigForm { $itemTypes = $this->getItemTypes(); $itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes); + $itemForm = $this->getItemForm($itemType); $this->addElement( 'text', @@ -444,7 +445,10 @@ class NavigationConfigForm extends ConfigForm ) ); - if ($this->getUser()->can('application/share/navigation')) { + if ( + (! $itemForm->requiresParentSelection() || !isset($formData['parent']) || !$formData['parent']) + && $this->getUser()->can('application/share/navigation') + ) { $checked = isset($formData['shared']) ? null : (isset($formData['users']) || isset($formData['groups'])); $this->addElement( @@ -495,9 +499,29 @@ class NavigationConfigForm extends ConfigForm ) ); - $itemForm = $this->getItemForm($itemType); + if ($itemForm->requiresParentSelection()) { + $availableParents = $this->listAvailableParents($itemType); + $this->addElement( + 'select', + 'parent', + array( + 'allowEmpty' => true, + 'autosubmit' => true, + 'label' => $this->translate('Parent'), + 'description' => $this->translate( + 'The parent item to assign this navigation item to. ' + . 'Select "None" to make this a main navigation item' + ), + 'multiOptions' => array_merge( + array('' => $this->translate('None', 'No parent for a navigation item')), + empty($availableParents) ? array() : array_combine($availableParents, $availableParents) + ) + ) + ); + } + $this->addSubForm($itemForm, 'item_form'); - $itemForm->create($formData); // Requires a parent which gets set by addSubForm() + $itemForm->create($formData); // May require a parent which gets set by addSubForm() } /** diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php index 813149d79..ff2429d2b 100644 --- a/application/forms/Navigation/NavigationItemForm.php +++ b/application/forms/Navigation/NavigationItemForm.php @@ -7,6 +7,23 @@ use Icinga\Web\Form; class NavigationItemForm extends Form { + /** + * Whether to create a select input to choose a parent for a navigation item of a particular type + * + * @var bool + */ + protected $requiresParentSelection = false; + + /** + * Return whether to create a select input to choose a parent for a navigation item of a particular type + * + * @return bool + */ + public function requiresParentSelection() + { + return $this->requiresParentSelection; + } + /** * {@inheritdoc} */ From 4b7fbdfa1fa670e0002d6c42fb7bc7a93ea6dbce Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 10:40:16 +0200 Subject: [PATCH 160/225] NavigationConfigForm: Provide proper parents when editing a shared item refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 33dab2bd4..744b7bb1b 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -186,7 +186,11 @@ class NavigationConfigForm extends ConfigForm public function listAvailableParents($type) { $shared = false; - if (($checkbox = $this->getElement('shared')) !== null) { + $children = array(); + if ($this->itemToLoad) { + $shared = $this->hasBeenShared($this->itemToLoad); + $children = $this->getFlattenedChildren($this->itemToLoad); + } elseif (($checkbox = $this->getElement('shared')) !== null) { if ($checkbox->isChecked()) { $shared = true; } else { @@ -195,8 +199,6 @@ class NavigationConfigForm extends ConfigForm } } - $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array(); - $names = array(); if ($shared) { foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { From 9477c53b43e8d3dcff6f53e34c51cf79441fc4db Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 10:52:03 +0200 Subject: [PATCH 161/225] NavigationConfigForm: Do not allow to choose a parent once a item is shared refs #5600 --- application/forms/Navigation/MenuItemForm.php | 9 ++++++--- application/forms/Navigation/NavigationConfigForm.php | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application/forms/Navigation/MenuItemForm.php b/application/forms/Navigation/MenuItemForm.php index 3cc296bfe..924d313a1 100644 --- a/application/forms/Navigation/MenuItemForm.php +++ b/application/forms/Navigation/MenuItemForm.php @@ -16,8 +16,11 @@ class MenuItemForm extends NavigationItemForm public function createElements(array $formData) { parent::createElements($formData); - $this->getParent()->getElement('parent')->setDescription($this->translate( - 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry' - )); + $parentElement = $this->getParent()->getElement('parent'); + if ($parentElement !== null) { + $parentElement->setDescription($this->translate( + 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry' + )); + } } } diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 744b7bb1b..f7aff35f2 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -431,6 +431,7 @@ class NavigationConfigForm extends ConfigForm */ public function createElements(array $formData) { + $shared = false; $itemTypes = $this->getItemTypes(); $itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes); $itemForm = $this->getItemForm($itemType); @@ -466,6 +467,7 @@ class NavigationConfigForm extends ConfigForm ); if ($checked || (isset($formData['shared']) && $formData['shared'])) { + $shared = true; $this->addElement( 'text', 'users', @@ -501,7 +503,7 @@ class NavigationConfigForm extends ConfigForm ) ); - if ($itemForm->requiresParentSelection()) { + if (! $shared && $itemForm->requiresParentSelection()) { $availableParents = $this->listAvailableParents($itemType); $this->addElement( 'select', From 335ed8c74fc9d0af089f731808e2fbefe9a102c0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 11:01:02 +0200 Subject: [PATCH 162/225] NavigationConfigForm: Provide all available parents for non-shared items refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 50 +++++++------------ 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index f7aff35f2..838beb68a 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -185,41 +185,27 @@ class NavigationConfigForm extends ConfigForm */ public function listAvailableParents($type) { - $shared = false; - $children = array(); - if ($this->itemToLoad) { - $shared = $this->hasBeenShared($this->itemToLoad); - $children = $this->getFlattenedChildren($this->itemToLoad); - } elseif (($checkbox = $this->getElement('shared')) !== null) { - if ($checkbox->isChecked()) { - $shared = true; - } else { - $requestData = $this->getRequestData(); - $shared = isset($requestData['shared']) && $requestData['shared']; + $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array(); + + $names = array(); + foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { + if ( + $sectionName !== $this->itemToLoad + && $sectionConfig->type === $type + && $sectionConfig->owner === $this->getUser()->getUsername() + && !in_array($sectionName, $children, true) + ) { + $names[] = $sectionName; } } - $names = array(); - if ($shared) { - foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { - if ( - $sectionName !== $this->itemToLoad - && $sectionConfig->type === $type - && $sectionConfig->owner === $this->getUser()->getUsername() - && !in_array($sectionName, $children, true) - ) { - $names[] = $sectionName; - } - } - } else { - foreach ($this->getUserConfig() as $sectionName => $sectionConfig) { - if ( - $sectionName !== $this->itemToLoad - && $sectionConfig->type === $type - && !in_array($sectionName, $children, true) - ) { - $names[] = $sectionName; - } + foreach ($this->getUserConfig() as $sectionName => $sectionConfig) { + if ( + $sectionName !== $this->itemToLoad + && $sectionConfig->type === $type + && !in_array($sectionName, $children, true) + ) { + $names[] = $sectionName; } } From 5eb7267b43c42ad93a88fde37ac56851522e2f0c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 11:10:17 +0200 Subject: [PATCH 163/225] user navigation overview: Display whether an item has been shared refs #5600 --- application/views/scripts/navigation/index.phtml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml index 796e3b7c1..7c81ba5a8 100644 --- a/application/views/scripts/navigation/index.phtml +++ b/application/views/scripts/navigation/index.phtml @@ -18,6 +18,7 @@ + @@ -34,6 +35,7 @@ + + parent): ?> + + + From 19ebdcfb377e76147f406272d51c64ede643abe9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 12:20:44 +0200 Subject: [PATCH 165/225] NavigationConfigForm: Fix method getFlattenedChildren() refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 838beb68a..3f0269fbe 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -227,8 +227,8 @@ class NavigationConfigForm extends ConfigForm } $children = array(); - foreach ($config as $sectionName => $sectionConfig) { - if ($sectionConfig->parent === $name) { + foreach ($config->toArray() as $sectionName => $sectionConfig) { + if (isset($sectionConfig['parent']) && $sectionConfig['parent'] === $name) { $children[] = $sectionName; $children = array_merge($children, $this->getFlattenedChildren($sectionName)); } From bef5eeda9357e0cee7aaa828c6fb048a583f21d3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 13:24:44 +0200 Subject: [PATCH 166/225] NavigationController: Fix action unshare() refs #5600 --- .../controllers/NavigationController.php | 27 +++++++++---------- .../views/scripts/navigation/shared.phtml | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index db02adb89..1716c98ef 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -277,26 +277,25 @@ class NavigationController extends Controller 'onSuccess' => function ($form) use ($navigationConfigForm) { try { $navigationConfigForm->unshare($form->getValue('name')); + if ($navigationConfigForm->save()) { + Notification::success(sprintf( + t('Navigation item "%s" has been unshared'), + $form->getValue('name') + )); + } else { + // TODO: It failed obviously to write one of the configs, so we're leaving the user in + // a inconsistent state. Luckily, it's nothing lost but possibly duplicated... + Notification::error(sprintf( + t('Failed to unshare navigation item "%s"'), + $form->getValue('name') + )); + } } catch (NotFoundError $e) { throw $e; } catch (Exception $e) { Notification::error($e->getMessage()); } - if ($navigationConfigForm->save()) { - Notification::success(sprintf( - t('Navigation item "%s" has been unshared'), - $form->getValue('name') - )); - } else { - // TODO: It failed obviously to write one of the configs, so we're leaving the user in - // a inconsistent state. Luckily, it's nothing lost but possibly duplicated... - Notification::error(sprintf( - t('Failed to unshare navigation item "%s"'), - $form->getValue('name') - )); - } - $redirect = $form->getValue('redirect'); if (! empty($redirect)) { $form->setRedirectUrl(htmlspecialchars_decode($redirect)); diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index a8bb69e13..7f148e3e3 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -38,8 +38,8 @@ 'block', sprintf( $this->translate( - 'This is a child of the navigation item %s. You can' - . ' only unshare this item by unsharing its parent' + 'This is a child of the navigation item %1$s. You can' + . ' only unshare this item by unsharing %1$s' ), $item->parent ) From 17e8f01d248ddae45910de8c3015b03588e7675a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 18 Sep 2015 15:34:12 +0200 Subject: [PATCH 167/225] Use the DN to fetch group memberships from LDAP fixes #9901 --- .../Authentication/User/LdapUserBackend.php | 7 +++++- .../UserGroup/LdapUserGroupBackend.php | 24 ++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index a33c23691..d8ea19ca2 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -364,7 +364,12 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In return false; } - return $this->ds->testCredentials($userDn, $password); + $testCredentialsResult = $this->ds->testCredentials($userDn, $password); + if ($testCredentialsResult) { + $user->setAdditional('ldap_dn', $userDn); + } + + return $testCredentialsResult; } catch (LdapException $e) { throw new AuthenticationException( 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 25fc27fb9..035005937 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -532,18 +532,20 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken */ public function getMemberships(User $user) { - $userQuery = $this->ds - ->select() - ->from($this->userClass) - ->where($this->userNameAttribute, $user->getUsername()) - ->setBase($this->userBaseDn) - ->setUsePagedResults(false); - if ($this->userFilter) { - $userQuery->where(new Expression($this->userFilter)); - } + if (($userDn = $user->getAdditional('ldap_dn')) === null) { + $userQuery = $this->ds + ->select() + ->from($this->userClass) + ->where($this->userNameAttribute, $user->getUsername()) + ->setBase($this->userBaseDn) + ->setUsePagedResults(false); + if ($this->userFilter) { + $userQuery->where(new Expression($this->userFilter)); + } - if (($userDn = $userQuery->fetchDn()) === null) { - return array(); + if (($userDn = $userQuery->fetchDn()) === null) { + return array(); + } } $groupQuery = $this->ds From 84dfbf0b084176e472e316f0ba2d7b7ec3524637 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 15:51:00 +0200 Subject: [PATCH 168/225] user navigation overview: Provide "owner" as sort column This is rather a quick&dirty solution than anything proper.. But it basically works and the only drawback is that ASC or DESC is dependent from the owner's name... refs #5600 --- application/controllers/NavigationController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 1716c98ef..cfd28cfba 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -86,6 +86,7 @@ class NavigationController extends Controller $this->setupSortControl( array( 'type' => $this->translate('Type'), + 'owner' => $this->translate('Shared'), 'name' => $this->translate('Shared Navigation') ), $query From f1c6b34d7ab34b7b8ae485771fa22a362cb4bfa1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 15:53:39 +0200 Subject: [PATCH 169/225] NavigationConfigForm: Automatically share childs if there parent is shared refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 3f0269fbe..dc5c3509e 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -283,6 +283,9 @@ class NavigationConfigForm extends ConfigForm unset($data['users']); unset($data['groups']); } + } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) { + $data['owner'] = $this->getUser()->getUsername(); + $config = $this->getShareConfig(); } $itemName = $data['name']; @@ -334,6 +337,13 @@ class NavigationConfigForm extends ConfigForm unset($data['users']); unset($data['groups']); } + } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) { + // Its parent is shared so should it itself + $config->removeSection($name); + $this->secondaryConfig = $config; + $config = $this->getShareConfig(); + $data['owner'] = $this->getUser()->getUsername(); + $shared = true; } if (isset($data['name'])) { From f4978fcb5a1e9f09dd76520ee6eac03632951971 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 15:56:00 +0200 Subject: [PATCH 170/225] NavigationConfigForm: Automatically unshare a child if its parent isn't shared refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index dc5c3509e..025e59fe3 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -322,7 +322,10 @@ class NavigationConfigForm extends ConfigForm $itemConfig = $config->getSection($name); if ($this->hasBeenShared($name)) { - if ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) { + if (isset($data['parent']) && $data['parent'] + ? !$this->hasBeenShared($data['parent']) + : ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) + ) { // It is shared but shouldn't anymore $config = $this->unshare($name)->config; // unshare() calls setIniConfig() } From 518cdad1a803258e5713646e4e51db8ec6222639 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 15:57:45 +0200 Subject: [PATCH 171/225] NavigationConfigForm: Automatically unshare childrens in method unshare() refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 025e59fe3..fafc665a8 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -327,7 +327,7 @@ class NavigationConfigForm extends ConfigForm : ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) ) { // It is shared but shouldn't anymore - $config = $this->unshare($name)->config; // unshare() calls setIniConfig() + $config = $this->unshare($name, isset($data['parent']) ? $data['parent'] : null)->config; } } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ($this->getUser()->can('application/share/navigation')) { @@ -393,12 +393,14 @@ class NavigationConfigForm extends ConfigForm * Unshare the given navigation item * * @param string $name + * @param string $parent * * @return $this * - * @throws NotFoundError In case no navigation item with the given name is found + * @throws NotFoundError In case no navigation item with the given name is found + * @throws IcingaException In case the navigation item has a parent assigned to it */ - public function unshare($name) + public function unshare($name, $parent = null) { $config = $this->getShareConfig(); if (! $config->hasSection($name)) { @@ -406,6 +408,20 @@ class NavigationConfigForm extends ConfigForm } $itemConfig = $config->getSection($name); + if ($parent === null) { + $parent = $itemConfig->parent; + } + + if ($parent && $this->hasBeenShared($parent)) { + throw new IcingaException( + 'Unable to unshare navigation item "%s". It is dependent from item "%s".' + . ' Dependent items can only be unshared by unsharing their parent', + $name, + $parent + ); + } + + $children = $this->getFlattenedChildren($name); $config->removeSection($name); $this->secondaryConfig = $config; @@ -416,6 +432,13 @@ class NavigationConfigForm extends ConfigForm $config = $owner->loadNavigationConfig(); } + foreach ($children as $child) { + $childConfig = $this->secondaryConfig->getSection($child); + unset($childConfig->owner); + $this->secondaryConfig->removeSection($child); + $config->setSection($child, $childConfig); + } + unset($itemConfig->owner); unset($itemConfig->users); unset($itemConfig->groups); From ed875b529753e63ddf91e8b776cd01f798a97f0b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 15:58:20 +0200 Subject: [PATCH 172/225] NavigationConfigForm: Do not allow to delete parents refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index fafc665a8..661e66a64 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -376,6 +376,8 @@ class NavigationConfigForm extends ConfigForm * @param string $name * * @return $this + * + * @throws IcingaException In case the navigation item has still children */ public function delete($name) { @@ -384,6 +386,15 @@ class NavigationConfigForm extends ConfigForm throw new NotFoundError('No navigation item called "%s" found', $name); } + $children = $this->getFlattenedChildren($name); + if (! empty($children)) { + throw new IcingaException( + 'Unable to delete navigation item "%s". There are other items dependent from it: %s', + $name, + join(', ', $children) + ); + } + $config->removeSection($name); $this->setIniConfig($config); return $this; From c077dbd15a7b592bacf82eb2b8c1ab8a6c5b55e0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 16:00:33 +0200 Subject: [PATCH 173/225] NavigationConfigForm: Update a parent's name on all childrens as well refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 661e66a64..e36f66b50 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -349,15 +349,26 @@ class NavigationConfigForm extends ConfigForm $shared = true; } + $oldName = null; if (isset($data['name'])) { if ($data['name'] !== $name) { $config->removeSection($name); + $oldName = $name; $name = $data['name']; } unset($data['name']); } + if ($oldName) { + // Update the parent name on all direct children + foreach ($config as $sectionConfig) { + if ($sectionConfig->parent === $oldName) { + $sectionConfig->parent = $name; + } + } + } + $itemConfig->merge($data); foreach ($itemConfig->toArray() as $k => $v) { if ($v === null) { From 9574f801607be0f14068f07ddcbf125eb2945503 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 16:08:58 +0200 Subject: [PATCH 174/225] NavigationConfigForm: Automatically share a parent's entire children refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index e36f66b50..853aab96e 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -317,10 +317,11 @@ class NavigationConfigForm extends ConfigForm $config = $this->getConfigForItem($name); if ($config === null) { throw new NotFoundError('No navigation item called "%s" found', $name); + } else { + $itemConfig = $config->getSection($name); } - $itemConfig = $config->getSection($name); - + $shared = false; if ($this->hasBeenShared($name)) { if (isset($data['parent']) && $data['parent'] ? !$this->hasBeenShared($data['parent']) @@ -336,6 +337,7 @@ class NavigationConfigForm extends ConfigForm $this->secondaryConfig = $config; $config = $this->getShareConfig(); $data['owner'] = $this->getUser()->getUsername(); + $shared = true; } else { unset($data['users']); unset($data['groups']); @@ -352,7 +354,6 @@ class NavigationConfigForm extends ConfigForm $oldName = null; if (isset($data['name'])) { if ($data['name'] !== $name) { - $config->removeSection($name); $oldName = $name; $name = $data['name']; } @@ -360,6 +361,23 @@ class NavigationConfigForm extends ConfigForm unset($data['name']); } + $itemConfig->merge($data); + foreach ($itemConfig->toArray() as $k => $v) { + if ($v === null) { + unset($itemConfig->$k); + } + } + + if ($shared) { + // Share all descendant children + foreach ($this->getFlattenedChildren($oldName ?: $name) as $child) { + $childConfig = $this->secondaryConfig->getSection($child); + $this->secondaryConfig->removeSection($child); + $childConfig->owner = $this->getUser()->getUsername(); + $config->setSection($child, $childConfig); + } + } + if ($oldName) { // Update the parent name on all direct children foreach ($config as $sectionConfig) { @@ -367,13 +385,12 @@ class NavigationConfigForm extends ConfigForm $sectionConfig->parent = $name; } } + + $config->removeSection($name); } - $itemConfig->merge($data); - foreach ($itemConfig->toArray() as $k => $v) { - if ($v === null) { - unset($itemConfig->$k); - } + if ($this->secondaryConfig !== null) { + $this->secondaryConfig->removeSection($oldName ?: $name); } $config->setSection($name, $itemConfig); From dd0e924e8d0882210ada1f3e2eaf527fdee16ed0 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 18 Sep 2015 16:11:51 +0200 Subject: [PATCH 175/225] Do not validate section names in forms fixes #10151 --- .../forms/Config/UserBackend/DbBackendForm.php | 14 -------------- .../Config/UserBackend/ExternalBackendForm.php | 14 -------------- .../forms/Config/UserBackend/LdapBackendForm.php | 14 -------------- .../Config/UserGroup/DbUserGroupBackendForm.php | 14 -------------- .../UserGroup/LdapUserGroupBackendForm.php | 14 -------------- application/forms/Dashboard/DashletForm.php | 16 +--------------- .../forms/Config/BackendConfigForm.php | 14 -------------- .../forms/Config/TransportConfigForm.php | 14 -------------- 8 files changed, 1 insertion(+), 113 deletions(-) diff --git a/application/forms/Config/UserBackend/DbBackendForm.php b/application/forms/Config/UserBackend/DbBackendForm.php index 49426c073..a683c838b 100644 --- a/application/forms/Config/UserBackend/DbBackendForm.php +++ b/application/forms/Config/UserBackend/DbBackendForm.php @@ -53,20 +53,6 @@ class DbBackendForm extends Form 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this authentication provider that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/application/forms/Config/UserBackend/ExternalBackendForm.php b/application/forms/Config/UserBackend/ExternalBackendForm.php index 4f3a4585f..317e35f81 100644 --- a/application/forms/Config/UserBackend/ExternalBackendForm.php +++ b/application/forms/Config/UserBackend/ExternalBackendForm.php @@ -32,20 +32,6 @@ class ExternalBackendForm extends Form 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this authentication provider that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The backend name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/application/forms/Config/UserBackend/LdapBackendForm.php b/application/forms/Config/UserBackend/LdapBackendForm.php index ac6765598..75634b6cb 100644 --- a/application/forms/Config/UserBackend/LdapBackendForm.php +++ b/application/forms/Config/UserBackend/LdapBackendForm.php @@ -57,20 +57,6 @@ class LdapBackendForm extends Form 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this authentication provider that is used to differentiate it from others.' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php index 9d7545fe7..5d1cf1839 100644 --- a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php @@ -34,20 +34,6 @@ class DbUserGroupBackendForm extends Form 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this user group backend that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 4f67b788b..4aeab8e1d 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -39,20 +39,6 @@ class LdapUserGroupBackendForm extends Form 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this user group backend that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/application/forms/Dashboard/DashletForm.php b/application/forms/Dashboard/DashletForm.php index 1e31a938d..d25001a23 100644 --- a/application/forms/Dashboard/DashletForm.php +++ b/application/forms/Dashboard/DashletForm.php @@ -77,21 +77,7 @@ class DashletForm extends Form array( 'required' => true, 'label' => $this->translate('Dashlet Title'), - 'description' => $this->translate('Enter a title for the dashlet.'), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\' or \']\'.' - ) - ) - ) - ) - ) + 'description' => $this->translate('Enter a title for the dashlet.') ) ); $this->addElement( diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php index d5a960d20..03c293d7c 100644 --- a/modules/monitoring/application/forms/Config/BackendConfigForm.php +++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php @@ -193,20 +193,6 @@ class BackendConfigForm extends ConfigForm 'label' => $this->translate('Backend Name'), 'description' => $this->translate( 'The name of this monitoring backend that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); diff --git a/modules/monitoring/application/forms/Config/TransportConfigForm.php b/modules/monitoring/application/forms/Config/TransportConfigForm.php index dc679c5da..f21fdd73d 100644 --- a/modules/monitoring/application/forms/Config/TransportConfigForm.php +++ b/modules/monitoring/application/forms/Config/TransportConfigForm.php @@ -217,20 +217,6 @@ class TransportConfigForm extends ConfigForm 'label' => $this->translate('Transport Name'), 'description' => $this->translate( 'The name of this command transport that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) ) ) ); From 75d003a77560c1b2f7c7a12d955aacfa72a1ba58 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 16:17:54 +0200 Subject: [PATCH 176/225] NavigationConfigForm: Automatically share a parent's entire children, #2 refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 853aab96e..225f2aa27 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -333,7 +333,6 @@ class NavigationConfigForm extends ConfigForm } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ($this->getUser()->can('application/share/navigation')) { // It is not shared yet but should be - $config->removeSection($name); $this->secondaryConfig = $config; $config = $this->getShareConfig(); $data['owner'] = $this->getUser()->getUsername(); @@ -344,7 +343,6 @@ class NavigationConfigForm extends ConfigForm } } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) { // Its parent is shared so should it itself - $config->removeSection($name); $this->secondaryConfig = $config; $config = $this->getShareConfig(); $data['owner'] = $this->getUser()->getUsername(); From 5f998eb56d51f8e57084f6d700cbabd08297ad95 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 16:25:06 +0200 Subject: [PATCH 177/225] NavigationConfigForm: Really remove the "old" section when renaming refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 225f2aa27..6e6675551 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -384,7 +384,7 @@ class NavigationConfigForm extends ConfigForm } } - $config->removeSection($name); + $config->removeSection($oldName); } if ($this->secondaryConfig !== null) { From f3df1f228de818cc4825434dc8f3b3d1dfc5a30c Mon Sep 17 00:00:00 2001 From: Jo Rhett Date: Fri, 18 Sep 2015 13:37:04 -0700 Subject: [PATCH 178/225] Fix for support issue 9950, do lookups properly on posixGroup group classes --- .../UserGroup/LdapUserGroupBackend.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 035005937..ea94ad8d8 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -543,15 +543,22 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $userQuery->where(new Expression($this->userFilter)); } - if (($userDn = $userQuery->fetchDn()) === null) { - return array(); + # Posix group only uses simple user name + if ($this->groupClass == 'posixGroup') { + $queryUsername = $user->getUsername(); + } + # LDAP groups use the complete DN + else { + if (($queryUsername = $userQuery->fetchDn()) === null) { + return array(); + } } } $groupQuery = $this->ds ->select() ->from($this->groupClass, array($this->groupNameAttribute)) - ->where($this->groupMemberAttribute, $userDn) + ->where($this->groupMemberAttribute, $queryUsername) ->setBase($this->groupBaseDn); if ($this->groupFilter) { $groupQuery->where(new Expression($this->groupFilter)); From 5a494b3088514ca8c2a68e092eaff596f0559e68 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 08:59:36 +0200 Subject: [PATCH 179/225] NavigationController: Show module item types only if accessible by the user refs #5600 --- application/controllers/NavigationController.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index cfd28cfba..335b05adc 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -47,11 +47,15 @@ class NavigationController extends Controller */ protected function listItemTypes() { + $moduleManager = Icinga::app()->getModuleManager(); + $types = $this->defaultItemTypes; - foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { - $moduleTypes = $module->getNavigationItems(); - if (! empty($moduleTypes)) { - $types = array_merge($types, $moduleTypes); + foreach ($moduleManager->getLoadedModules() as $module) { + if ($this->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) { + $moduleTypes = $module->getNavigationItems(); + if (! empty($moduleTypes)) { + $types = array_merge($types, $moduleTypes); + } } } From af3f80f8732602e51bb49389ccfcc4d1afe8e612 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 09:00:22 +0200 Subject: [PATCH 180/225] NavigationConfigForm: Fix documentation of method delete() refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 6e6675551..a7547dfae 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -403,6 +403,7 @@ class NavigationConfigForm extends ConfigForm * * @return $this * + * @throws NotFoundError In case no navigation item with the given name is found * @throws IcingaException In case the navigation item has still children */ public function delete($name) From 76cf01869e41a82939c1903aecaa5a2f7fb25fd9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 10:18:46 +0200 Subject: [PATCH 181/225] NavigationItem: Add property $target refs #5600 --- .../forms/Navigation/NavigationItemForm.php | 20 +++++++- .../Icinga/Web/Navigation/NavigationItem.php | 30 +++++++++++ .../Renderer/NavigationItemRenderer.php | 51 ++++++++----------- 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php index ff2429d2b..c7cb43422 100644 --- a/application/forms/Navigation/NavigationItemForm.php +++ b/application/forms/Navigation/NavigationItemForm.php @@ -29,6 +29,22 @@ class NavigationItemForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'select', + 'target', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Target'), + 'description' => $this->translate('The target where to open this navigation item\'s url'), + 'multiOptions' => array( + '_blank' => $this->translate('New Window'), + '_next' => $this->translate('New Column'), + '_main' => $this->translate('Single Column'), + '_self' => $this->translate('Current Column') + ) + ) + ); + $this->addElement( 'text', 'url', @@ -36,7 +52,7 @@ class NavigationItemForm extends Form 'allowEmpty' => true, 'label' => $this->translate('Url'), 'description' => $this->translate( - 'The url of this navigation item. Leave blank if you only want the name being displayed.' + 'The url of this navigation item. Leave blank if you only want the name being displayed' ) ) ); @@ -48,7 +64,7 @@ class NavigationItemForm extends Form 'allowEmpty' => true, 'label' => $this->translate('Icon'), 'description' => $this->translate( - 'The icon of this navigation item. Leave blank if you do not want a icon being displayed.' + 'The icon of this navigation item. Leave blank if you do not want a icon being displayed' ) ) ); diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index c1e8b46b0..ba6197ce4 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -94,6 +94,13 @@ class NavigationItem implements IteratorAggregate */ protected $url; + /** + * This item's url target + * + * @var string + */ + protected $target; + /** * Additional parameters for this item's url * @@ -445,6 +452,29 @@ class NavigationItem implements IteratorAggregate return $this; } + /** + * Set this item's url target + * + * @param string $target + * + * @return $this + */ + public function setTarget($target) + { + $this->target = $target; + return $this; + } + + /** + * Return this item's url target + * + * @return string + */ + public function getTarget() + { + return $this->target; + } + /** * Return this item's url * diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 70c89c5b7..2de90dae0 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -28,13 +28,6 @@ class NavigationItemRenderer */ protected $item; - /** - * The link target - * - * @var string - */ - protected $target; - /** * Create a new NavigationItemRenderer * @@ -124,29 +117,6 @@ class NavigationItemRenderer return $this->item; } - /** - * Set the link target - * - * @param string $target - * - * @return $this - */ - public function setTarget($target) - { - $this->target = $target; - return $this; - } - - /** - * Return the link target - * - * @return string - */ - public function getTarget() - { - return $this->target; - } - /** * Render the given navigation item as HTML anchor * @@ -175,7 +145,7 @@ class NavigationItemRenderer '%s', $this->view()->propertiesToString($item->getAttributes()), $this->view()->url($url, $item->getUrlParameters()), - $this->target ? ' target="' . $this->view()->escape($this->target) . '"' : '', + $this->renderTargetAttribute(), $label ); } else { @@ -189,4 +159,23 @@ class NavigationItemRenderer return $content; } + + /** + * Render and return the attribute to provide a non-default target for the url + * + * @return string + */ + protected function renderTargetAttribute() + { + $target = $this->getItem()->getTarget(); + if ($target === null) { + return ''; + } + + if (! in_array($target, array('_main', '_self', '_next'))) { + return ' target="' . $this->view()->escape($target) . '"'; + } + + return ' data-base-target="' . $target . '"'; + } } From 3aaf726856d84c48907fd7ef2d152e6fdeeac3ed Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 11:13:05 +0200 Subject: [PATCH 182/225] Update CSS style used for the navigation refs #5600 --- .../Renderer/NavigationRenderer.php | 20 ++++++++----------- .../Renderer/NavigationRendererInterface.php | 15 +++++++++++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index 4818f402e..8b5caf581 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -304,10 +304,7 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa */ public function beginItemMarkup(NavigationItem $item) { - $cssClass = array(); - if ($item->getActive()) { - $cssClass[] = static::CSS_CLASS_ACTIVE; - } + $cssClass = array(static::CSS_CLASS_ITEM); if ($item->hasChildren() && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN) { $cssClass[] = static::CSS_CLASS_DROPDOWN; @@ -317,16 +314,15 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa ->setUrl('#'); } - if (! empty($cssClass)) { - $content = sprintf( - '
      • ', - $this->view()->escape($item->getUniqueName()), - join(' ', $cssClass) - ); - } else { - $content = '
      • '; + if ($item->getActive()) { + $cssClass[] = static::CSS_CLASS_ACTIVE; } + $content = sprintf( + '
      • ', + $this->view()->escape($item->getUniqueName()), + join(' ', $cssClass) + ); return $content; } diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php index 4e8ce6414..0f0e5acf1 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php @@ -8,6 +8,13 @@ namespace Icinga\Web\Navigation\Renderer; */ interface NavigationRendererInterface { + /** + * CSS class for items + * + * @var string + */ + const CSS_CLASS_ITEM = 'nav-item'; + /** * CSS class for active items * @@ -20,10 +27,12 @@ interface NavigationRendererInterface * * @var string */ - const CSS_CLASS_DROPDOWN = 'dropdown'; + const CSS_CLASS_DROPDOWN = 'dropdown-nav-item'; /** * CSS class for a dropdown item's trigger + * + * @var string */ const CSS_CLASS_DROPDOWN_TOGGLE = 'dropdown-toggle'; @@ -39,14 +48,14 @@ interface NavigationRendererInterface * * @var string */ - const CSS_CLASS_NAV_DROPDOWN = 'dropdown-menu'; + const CSS_CLASS_NAV_DROPDOWN = 'dropdown-nav'; /** * CSS class for the ul element with tabs layout * * @var string */ - const CSS_CLASS_NAV_TABS = 'nav-tabs'; + const CSS_CLASS_NAV_TABS = 'tab-nav'; /** * Icon for a dropdown item's trigger From c8d3aa25171ff1eb079ce83492d03c4262ae16dc Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 13:10:35 +0200 Subject: [PATCH 183/225] Url: Support external urls in fromPath() refs #5600 --- library/Icinga/Web/Url.php | 55 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 6d61e60a1..3a07c9034 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -13,12 +13,17 @@ use Icinga\Data\Filter\Filter; * returns Urls reflecting all changes made to the url and to the parameters. * * Direct instantiation is prohibited and should be done either with @see Url::fromRequest() or - * @see Url::fromUrlString() - * - * Currently, protocol, host and port are ignored and will be implemented when required + * @see Url::fromPath() */ class Url { + /** + * Whether this url points to an external resource + * + * @var bool + */ + protected $external; + /** * An array of all parameters stored in this Url * @@ -132,8 +137,6 @@ class Url } $urlObject = new Url(); - $baseUrl = $request->getBaseUrl(); - $urlObject->setBaseUrl($baseUrl); if ($url === '#') { $urlObject->setPath($url); @@ -141,8 +144,25 @@ class Url } $urlParts = parse_url($url); + if (isset($urlParts['scheme']) && $urlParts['scheme'] !== $request->getScheme()) { + $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port']) + ? (':' . $urlParts['port']) + : ''); + $urlObject->setIsExternal(); + } elseif ( + (isset($urlParts['host]']) && $urlParts['host'] !== $request->getServer('SERVER_NAME')) + || (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')) + ) { + $baseUrl = $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) : ''); + $urlObject->setIsExternal(); + } else { + $baseUrl = $request->getBaseUrl(); + } + + $urlObject->setBaseUrl($baseUrl); + if (isset($urlParts['path'])) { - if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) { + if ($baseUrl && !$urlObject->isExternal() && strpos($urlParts['path'], $baseUrl) === 0) { $urlObject->setPath(substr($urlParts['path'], strlen($baseUrl))); } else { $urlObject->setPath($urlParts['path']); @@ -236,6 +256,29 @@ class Url return $this->path; } + /** + * Set whether this url points to an external resource + * + * @param bool $state + * + * @return $this + */ + public function setIsExternal($state = true) + { + $this->external = (bool) $state; + return $this; + } + + /** + * Return whether this url points to an external resource + * + * @return bool + */ + public function isExternal() + { + return $this->external; + } + /** * Return the relative url with query parameters as a string * From 8bfc7b8805150f4068a8957216f85b30691f9c15 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 21 Sep 2015 13:26:42 +0200 Subject: [PATCH 184/225] Repair "Show More" links in the Alert Summary fixes #9995 --- .../controllers/AlertsummaryController.php | 12 +++++++++--- .../views/scripts/alertsummary/index.phtml | 10 ++++++---- .../views/scripts/list/notifications.phtml | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/monitoring/application/controllers/AlertsummaryController.php b/modules/monitoring/application/controllers/AlertsummaryController.php index 88100d01b..dc28d9035 100644 --- a/modules/monitoring/application/controllers/AlertsummaryController.php +++ b/modules/monitoring/application/controllers/AlertsummaryController.php @@ -57,7 +57,9 @@ class AlertsummaryController extends Controller $this->view->title = $this->translate('Alert Summary'); $this->view->intervalBox = $this->createIntervalBox(); - $this->view->recentAlerts = $this->createRecentAlerts(); + list($recentAlerts, $recentAlertsUrl) = $this->createRecentAlerts(); + $this->view->recentAlerts = $recentAlerts; + $this->view->recentAlertsUrl = $recentAlertsUrl; $this->view->interval = $this->getInterval(); $this->view->defectChart = $this->createDefectImage(); $this->view->healingChart = $this->createHealingChart(); @@ -80,6 +82,7 @@ class AlertsummaryController extends Controller ); $this->applyRestriction('monitoring/filter/objects', $query); $this->view->notifications = $query; + $this->view->notificationsUrl = 'monitoring/list/notifications'; $this->setupLimitControl(); $this->setupPaginationControl($this->view->notifications); @@ -487,7 +490,7 @@ class AlertsummaryController extends Controller /** * Top recent alerts * - * @return mixed + * @return array */ private function createRecentAlerts() { @@ -508,7 +511,10 @@ class AlertsummaryController extends Controller $query->order('notification_start_time', 'desc'); - return $query->limit(5); + return array( + $query->limit(5), + 'monitoring/list/notifications?sort=notification_start_time&dir=desc' + ); } /** diff --git a/modules/monitoring/application/views/scripts/alertsummary/index.phtml b/modules/monitoring/application/views/scripts/alertsummary/index.phtml index d96ca5bdc..f84395000 100644 --- a/modules/monitoring/application/views/scripts/alertsummary/index.phtml +++ b/modules/monitoring/application/views/scripts/alertsummary/index.phtml @@ -59,8 +59,9 @@
        partial('list/notifications.phtml', array( - 'notifications' => $this->recentAlerts, - 'compact' => true + 'notifications' => $this->recentAlerts, + 'compact' => true, + 'notificationsUrl' => $recentAlertsUrl )); ?>
        @@ -70,8 +71,9 @@
        partial('list/notifications.phtml', array( - 'notifications' => $this->notifications, - 'compact' => true + 'notifications' => $this->notifications, + 'compact' => true, + 'notificationsUrl' => $notificationsUrl )); ?>
        diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml index 06028c993..6e08abaa7 100644 --- a/modules/monitoring/application/views/scripts/list/notifications.phtml +++ b/modules/monitoring/application/views/scripts/list/notifications.phtml @@ -72,7 +72,7 @@ if (! $this->compact): ?> hasMore()): ?> qlink( $this->translate('Show More'), - $this->url()->without(array('view', 'limit')), + $this->url(isset($notificationsUrl) ? $notificationsUrl : null)->without(array('view', 'limit')), null, array( 'data-base-target' => '_next', From 776d06d6ca8ec0f9387341154de94397b9602d13 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 14:03:53 +0200 Subject: [PATCH 185/225] Url: Fix host check in fromPath() refs #5600 --- library/Icinga/Web/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 3a07c9034..0a7996bc1 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -150,7 +150,7 @@ class Url : ''); $urlObject->setIsExternal(); } elseif ( - (isset($urlParts['host]']) && $urlParts['host'] !== $request->getServer('SERVER_NAME')) + (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME')) || (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')) ) { $baseUrl = $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) : ''); From 7e460e7c03486131866e7009479752ee138d5e8c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 14:04:36 +0200 Subject: [PATCH 186/225] Url: Remove redundant slash on urls without path --- library/Icinga/Web/Url.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 0a7996bc1..6a6b1dbcb 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -316,7 +316,7 @@ class Url return $relativeUrl; } - return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $relativeUrl; + return $this->baseUrl . ($this->baseUrl !== '/' && $relativeUrl ? '/' : '') . $relativeUrl; } /** From 3c7155f246bd28126d7a8a4579827c89ad481fca Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 14:13:10 +0200 Subject: [PATCH 187/225] NavigationItemRenderer: Pass external urls through the iframe module refs #5600 --- .../Renderer/NavigationItemRenderer.php | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php index 2de90dae0..7e7a0fd55 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php @@ -7,6 +7,7 @@ use Icinga\Application\Icinga; use Icinga\Exception\ProgrammingError; use Icinga\Util\String; use Icinga\Web\Navigation\NavigationItem; +use Icinga\Web\Url; use Icinga\Web\View; /** @@ -28,6 +29,13 @@ class NavigationItemRenderer */ protected $item; + /** + * Internal link targets provided by Icinga Web 2 + * + * @var array + */ + protected $internalLinkTargets; + /** * Create a new NavigationItemRenderer * @@ -39,6 +47,7 @@ class NavigationItemRenderer $this->setOptions($options); } + $this->internalLinkTargets = array('_main', '_self', '_next'); $this->init(); } @@ -141,10 +150,17 @@ class NavigationItemRenderer } if (($url = $item->getUrl()) !== null) { + $url->overwriteParams($item->getUrlParameters()); + + $target = $item->getTarget(); + if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) { + $url = Url::fromPath('iframe', array('url' => $url)); + } + $content = sprintf( '%s', $this->view()->propertiesToString($item->getAttributes()), - $this->view()->url($url, $item->getUrlParameters()), + $url, $this->renderTargetAttribute(), $label ); @@ -172,7 +188,7 @@ class NavigationItemRenderer return ''; } - if (! in_array($target, array('_main', '_self', '_next'))) { + if (! in_array($target, $this->internalLinkTargets, true)) { return ' target="' . $this->view()->escape($target) . '"'; } From 3f70351239275930943b565302457184bfc6196b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 15:26:56 +0200 Subject: [PATCH 188/225] MenuItemForm: Only allow to select _blank and _main as url target refs #5600 --- application/forms/Navigation/MenuItemForm.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/forms/Navigation/MenuItemForm.php b/application/forms/Navigation/MenuItemForm.php index 924d313a1..583ae4753 100644 --- a/application/forms/Navigation/MenuItemForm.php +++ b/application/forms/Navigation/MenuItemForm.php @@ -16,6 +16,11 @@ class MenuItemForm extends NavigationItemForm public function createElements(array $formData) { parent::createElements($formData); + + // Remove _self and _next as for menu entries only _main is valid + $this->getElement('target')->removeMultiOption('_self'); + $this->getElement('target')->removeMultiOption('_next'); + $parentElement = $this->getParent()->getElement('parent'); if ($parentElement !== null) { $parentElement->setDescription($this->translate( From 8239ec8fb594d97f89c97f9a9a53604f4d61b63b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 21 Sep 2015 15:29:14 +0200 Subject: [PATCH 189/225] NavigationConfigForm: Show only an owner's parents to admins refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index a7547dfae..4f4f3a975 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -181,9 +181,12 @@ class NavigationConfigForm extends ConfigForm /** * Return a list of available parent items for the given type of navigation item * + * @param string $type + * @param string $owner + * * @return array */ - public function listAvailableParents($type) + public function listAvailableParents($type, $owner = null) { $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array(); @@ -192,7 +195,7 @@ class NavigationConfigForm extends ConfigForm if ( $sectionName !== $this->itemToLoad && $sectionConfig->type === $type - && $sectionConfig->owner === $this->getUser()->getUsername() + && $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername()) && !in_array($sectionName, $children, true) ) { $names[] = $sectionName; @@ -564,7 +567,13 @@ class NavigationConfigForm extends ConfigForm ); if (! $shared && $itemForm->requiresParentSelection()) { - $availableParents = $this->listAvailableParents($itemType); + if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) { + $itemConfig = $this->getShareConfig()->getSection($this->itemToLoad); + $availableParents = $this->listAvailableParents($itemType, $itemConfig->owner); + } else { + $availableParents = $this->listAvailableParents($itemType); + } + $this->addElement( 'select', 'parent', From 84554d245dfc20e9623bd4be34b1b8b6244c4a2e Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 22 Sep 2015 12:51:00 +0200 Subject: [PATCH 190/225] Conform to coding guidelines refs #9950 --- .../Authentication/UserGroup/LdapUserGroupBackend.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index ea94ad8d8..de25a7c3c 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -543,12 +543,11 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $userQuery->where(new Expression($this->userFilter)); } - # Posix group only uses simple user name - if ($this->groupClass == 'posixGroup') { + if ($this->groupClass === 'posixGroup') { + # Posix group only uses simple user name $queryUsername = $user->getUsername(); - } - # LDAP groups use the complete DN - else { + } else { + # LDAP groups use the complete DN if (($queryUsername = $userQuery->fetchDn()) === null) { return array(); } From 46f2f71c57ea8686a72f42b028b40b704ffdd30b Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 22 Sep 2015 13:02:08 +0200 Subject: [PATCH 191/225] Improve logging of membership queries refs #9950 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index de25a7c3c..21dea70ae 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -12,6 +12,7 @@ use Icinga\Protocol\Ldap\Expression; use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\User; +use Icinga\Application\Logger; class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface { @@ -563,10 +564,12 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $groupQuery->where(new Expression($this->groupFilter)); } + Logger::debug('Fetching groups for user %s using filter %s.', $user->getUsername(), $groupQuery->__toString()); $groups = array(); foreach ($groupQuery as $row) { $groups[] = $row->{$this->groupNameAttribute}; } + Logger::debug('Fetched %d groups: %s.', count($groups), join(', ', $groups)); return $groups; } From 42fb1a174b431ba6a1fe52770ad625c09875ab6e Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 22 Sep 2015 14:08:15 +0200 Subject: [PATCH 192/225] Do not crash when ldap_dn is defined in additional variables refs #9950 --- .../UserGroup/LdapUserGroupBackend.php | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 21dea70ae..490fa44db 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -533,23 +533,23 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken */ public function getMemberships(User $user) { - if (($userDn = $user->getAdditional('ldap_dn')) === null) { - $userQuery = $this->ds - ->select() - ->from($this->userClass) - ->where($this->userNameAttribute, $user->getUsername()) - ->setBase($this->userBaseDn) - ->setUsePagedResults(false); - if ($this->userFilter) { - $userQuery->where(new Expression($this->userFilter)); - } + if ($this->groupClass === 'posixGroup') { + # Posix group only uses simple user name + $userDn = $user->getUsername(); + } else { + # LDAP groups use the complete DN + if (($userDn = $user->getAdditional('ldap_dn')) === null) { + $userQuery = $this->ds + ->select() + ->from($this->userClass) + ->where($this->userNameAttribute, $user->getUsername()) + ->setBase($this->userBaseDn) + ->setUsePagedResults(false); + if ($this->userFilter) { + $userQuery->where(new Expression($this->userFilter)); + } - if ($this->groupClass === 'posixGroup') { - # Posix group only uses simple user name - $queryUsername = $user->getUsername(); - } else { - # LDAP groups use the complete DN - if (($queryUsername = $userQuery->fetchDn()) === null) { + if (($userDn = $userQuery->fetchDn()) === null) { return array(); } } @@ -558,7 +558,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $groupQuery = $this->ds ->select() ->from($this->groupClass, array($this->groupNameAttribute)) - ->where($this->groupMemberAttribute, $queryUsername) + ->where($this->groupMemberAttribute, $userDn) ->setBase($this->groupBaseDn); if ($this->groupFilter) { $groupQuery->where(new Expression($this->groupFilter)); From 57e08e92d4f9ae7947b96dfa445939d684f488f3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 14:12:08 +0200 Subject: [PATCH 193/225] Do not mark relative urls as absolute refs #5600 refs #10169 --- application/controllers/PreferenceController.php | 2 +- application/layouts/scripts/body.phtml | 2 +- application/layouts/scripts/layout.phtml | 2 +- application/views/scripts/config/resource.phtml | 2 +- .../views/scripts/config/userbackend/reorder.phtml | 2 +- .../application/views/scripts/config/index.phtml | 12 ++++++------ .../application/views/scripts/list/comments.phtml | 2 +- .../views/scripts/list/contactgroups.phtml | 2 +- .../application/views/scripts/list/contacts.phtml | 2 +- .../application/views/scripts/list/downtimes.phtml | 2 +- .../application/views/scripts/timeline/index.phtml | 2 +- modules/monitoring/configuration.php | 4 ++-- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/application/controllers/PreferenceController.php b/application/controllers/PreferenceController.php index 6437a7c97..6bf407843 100644 --- a/application/controllers/PreferenceController.php +++ b/application/controllers/PreferenceController.php @@ -32,7 +32,7 @@ class PreferenceController extends BasePreferenceController array( 'title' => t('Adjust the preferences of Icinga Web 2 according to your needs'), 'label' => t('Preferences'), - 'url' => Url::fromPath('/preference') + 'url' => Url::fromPath('preference') ) ) ); diff --git a/application/layouts/scripts/body.phtml b/application/layouts/scripts/body.phtml index 325b1026b..fe1f7bd28 100644 --- a/application/layouts/scripts/body.phtml +++ b/application/layouts/scripts/body.phtml @@ -22,7 +22,7 @@ if ($this->layout()->autorefreshInterval) { isAuthenticated()): ?> qlink( '', - '/dashboard', + 'dashboard', null, array( 'icon' => '../logo_icinga-inv.png', diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml index efe1aa9cc..b2c14dba0 100644 --- a/application/layouts/scripts/layout.phtml +++ b/application/layouts/scripts/layout.phtml @@ -63,7 +63,7 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml'; diff --git a/application/views/scripts/config/resource.phtml b/application/views/scripts/config/resource.phtml index 740e548d9..e431d7c33 100644 --- a/application/views/scripts/config/resource.phtml +++ b/application/views/scripts/config/resource.phtml @@ -2,7 +2,7 @@
      • translate('Navigation'); ?> translate('Type'); ?>translate('Shared'); ?> translate('Remove'); ?>
        type && isset($types[$item->type]) ? $this->escape($types[$item->type]) : $this->escape($this->translate('Unknown')); ?>owner ? $this->translate('Yes') : $this->translate('No'); ?> qlink( '', 'navigation/remove', From 37c7952ec75990cae9defc2f6589c1d2655c22d2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 18 Sep 2015 11:34:00 +0200 Subject: [PATCH 164/225] shared navigation overview: Do not allow to unshare childs individually refs #5600 --- application/views/scripts/navigation/shared.phtml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index 0d7555020..a8bb69e13 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -33,7 +33,20 @@ ? $this->escape($types[$item->type]) : $this->escape($this->translate('Unknown')); ?> escape($item->owner); ?>icon( + 'block', + sprintf( + $this->translate( + 'This is a child of the navigation item %s. You can' + . ' only unshare this item by unsharing its parent' + ), + $item->parent + ) + ); ?> setDefault('name', $name); ?>
        diff --git a/application/views/scripts/config/userbackend/reorder.phtml b/application/views/scripts/config/userbackend/reorder.phtml index 08b5f19be..174840d23 100644 --- a/application/views/scripts/config/userbackend/reorder.phtml +++ b/application/views/scripts/config/userbackend/reorder.phtml @@ -2,7 +2,7 @@
        - + icon('plus'); ?>translate('Create A New User Backend'); ?>
        diff --git a/modules/monitoring/application/views/scripts/config/index.phtml b/modules/monitoring/application/views/scripts/config/index.phtml index 8f5e49427..3ce261840 100644 --- a/modules/monitoring/application/views/scripts/config/index.phtml +++ b/modules/monitoring/application/views/scripts/config/index.phtml @@ -5,7 +5,7 @@
        qlink( $backendName, - '/monitoring/config/editbackend', + 'monitoring/config/editbackend', array('backend-name' => $backendName), array( 'icon' => 'edit', @@ -35,7 +35,7 @@ qlink( '', - '/monitoring/config/removebackend', + 'monitoring/config/removebackend', array('backend-name' => $backendName), array( 'icon' => 'trash', @@ -49,7 +49,7 @@

        translate('Command Transports') ?>

        - + icon('plus'); ?> translate('Create New Transport'); ?>

        @@ -64,7 +64,7 @@
        qlink( $transportName, - '/monitoring/config/edittransport', + 'monitoring/config/edittransport', array('transport' => $transportName), array( 'icon' => 'edit', @@ -79,7 +79,7 @@ qlink( '', - '/monitoring/config/removetransport', + 'monitoring/config/removetransport', array('transport' => $transportName), array( 'icon' => 'trash', diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml index 44b452f42..cfe028334 100644 --- a/modules/monitoring/application/views/scripts/list/comments.phtml +++ b/modules/monitoring/application/views/scripts/list/comments.phtml @@ -14,7 +14,7 @@
        peekAhead($this->compact) as $comment): ?> diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml index 3030d4224..099db3979 100644 --- a/modules/monitoring/application/views/scripts/list/contactgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml @@ -27,7 +27,7 @@ if (count($groupData) === 0) {
        - img('/static/gravatar', array('email' => $c->contact_email)); ?> + img('static/gravatar', array('email' => $c->contact_email)); ?> qlink( $c->contact_alias, 'monitoring/show/contact', diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml index 3e10197fb..4cb32101d 100644 --- a/modules/monitoring/application/views/scripts/list/contacts.phtml +++ b/modules/monitoring/application/views/scripts/list/contacts.phtml @@ -10,7 +10,7 @@
        peekAhead($this->compact) as $contact): ?>
        - img('/static/gravatar', array('email' => $contact->contact_email)); ?> + img('static/gravatar', array('email' => $contact->contact_email)); ?> qlink( $contact->contact_name, 'monitoring/show/contact', diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml index a45bd0499..93c317d6a 100644 --- a/modules/monitoring/application/views/scripts/list/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml @@ -20,7 +20,7 @@ if (! $this->compact): ?>
        diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml index 209eb0f1b..56312828d 100644 --- a/modules/monitoring/application/views/scripts/timeline/index.phtml +++ b/modules/monitoring/application/views/scripts/timeline/index.phtml @@ -65,7 +65,7 @@ if (! $beingExtended && !$this->compact): ?>
        qlink( $timeInfo[0]->end->format($intervalFormat), - '/monitoring/list/eventhistory', + 'monitoring/list/eventhistory', array( 'timestamp<' => $timeInfo[0]->start->getTimestamp(), 'timestamp>' => $timeInfo[0]->end->getTimestamp() diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 2d54137d4..c1c6d5b69 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -263,11 +263,11 @@ $dashboard->add( ); $dashboard->add( N_('Service Groups'), - '/monitoring/list/servicegroups' + 'monitoring/list/servicegroups' ); $dashboard->add( N_('Host Groups'), - '/monitoring/list/hostgroups' + 'monitoring/list/hostgroups' ); /* From 7321d67b4f8387753718656469e73e347f0010d0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 14:14:41 +0200 Subject: [PATCH 194/225] Url: Consider urls with a leading slash as absolute.. ..and make it possible to have a url without a base. refs #5600 --- library/Icinga/Web/Url.php | 99 +++++++++++++++++-------- test/php/library/Icinga/Web/UrlTest.php | 6 +- 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 6a6b1dbcb..6f39bbab7 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -46,12 +46,11 @@ class Url protected $path = ''; /** - * The baseUrl that will be appended to @see Url::$path in order to - * create an absolute Url + * The baseUrl that will be appended to @see Url::$path * * @var string */ - protected $baseUrl = '/'; + protected $baseUrl = ''; protected function __construct() { @@ -156,17 +155,24 @@ class Url $baseUrl = $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) : ''); $urlObject->setIsExternal(); } else { - $baseUrl = $request->getBaseUrl(); + $baseUrl = ''; } - $urlObject->setBaseUrl($baseUrl); - if (isset($urlParts['path'])) { - if ($baseUrl && !$urlObject->isExternal() && strpos($urlParts['path'], $baseUrl) === 0) { - $urlObject->setPath(substr($urlParts['path'], strlen($baseUrl))); - } else { - $urlObject->setPath($urlParts['path']); + $urlPath = $urlParts['path']; + if ($urlPath && $urlPath[0] === '/') { + $baseUrl = ''; + } elseif (! $baseUrl) { + $baseUrl = $request->getBaseUrl(); } + + if ($baseUrl && !$urlObject->isExternal() && strpos($urlPath, $baseUrl) === 0) { + $urlObject->setPath(substr($urlPath, strlen($baseUrl))); + } else { + $urlObject->setPath($urlPath); + } + } elseif (! $baseUrl) { + $baseUrl = $request->getBaseUrl(); } // TODO: This has been used by former filter implementation, remove it: @@ -178,6 +184,7 @@ class Url $urlObject->setAnchor($urlParts['fragment']); } + $urlObject->setBaseUrl($baseUrl); $urlObject->setParams($params); return $urlObject; } @@ -205,19 +212,13 @@ class Url /** * Overwrite the baseUrl * - * If an empty Url is given '/' is used as the base - * * @param string $baseUrl The url path to use as the Url Base * * @return $this */ public function setBaseUrl($baseUrl) { - if (($baseUrl = rtrim($baseUrl, '/ ')) === '') { - $baseUrl = '/'; - } - - $this->baseUrl = $baseUrl; + $this->baseUrl = rtrim($baseUrl, '/ '); return $this; } @@ -280,17 +281,42 @@ class Url } /** - * Return the relative url with query parameters as a string + * Return the relative url * * @return string + * + * @throws ProgrammingError In case no relative url path is set or it is absolute */ public function getRelativeUrl($separator = '&') { - if ($this->params->isEmpty()) { - return $this->path . $this->anchor; - } else { - return $this->path . '?' . $this->params->toString($separator) . $this->anchor; + $path = $this->getPath(); + if (! $path) { + throw new ProgrammingError('Unable to provide a relative URL. No path set'); + } elseif ($path[0] === '/') { + throw new ProgrammingError('Cannot provide a relative URL. Path is absolute'); } + + return $this->buildPathQueryAndFragment($separator); + } + + /** + * Return this url's path with its query parameters and fragment as string + * + * @return string + */ + protected function buildPathQueryAndFragment($querySeparator) + { + $anchor = $this->getAnchor(); + if ($anchor) { + $anchor = '#' . $anchor; + } + + $query = $this->getQueryString($querySeparator); + if ($query) { + $query = '?' . $query; + } + + return $this->getPath() . $query . $anchor; } public function setQueryString($queryString) @@ -299,9 +325,9 @@ class Url return $this; } - public function getQueryString() + public function getQueryString($separator = null) { - return (string) $this->params; + return $this->params->toString($separator); } /** @@ -311,12 +337,17 @@ class Url */ public function getAbsoluteUrl($separator = '&') { - $relativeUrl = $this->getRelativeUrl($separator); - if ($relativeUrl === '#') { - return $relativeUrl; + $path = $this->buildPathQueryAndFragment($separator); + if ($path && ($path === '#' || $path[0] === '/')) { + return $path; } - return $this->baseUrl . ($this->baseUrl !== '/' && $relativeUrl ? '/' : '') . $relativeUrl; + $baseUrl = $this->getBaseUrl(); + if (! $baseUrl) { + $baseUrl = '/'; + } + + return $baseUrl . ($baseUrl !== '/' && $path ? '/' : '') . $path; } /** @@ -434,10 +465,20 @@ class Url */ public function setAnchor($anchor) { - $this->anchor = '#' . $anchor; + $this->anchor = $anchor; return $this; } + /** + * Return the url anchor-part + * + * @return string The site's anchor string without the '#' + */ + public function getAnchor() + { + return $this->anchor; + } + /** * Remove provided key (if string) or keys (if array of string) from the query parameter array * diff --git a/test/php/library/Icinga/Web/UrlTest.php b/test/php/library/Icinga/Web/UrlTest.php index ad989ded5..5dd3b7045 100644 --- a/test/php/library/Icinga/Web/UrlTest.php +++ b/test/php/library/Icinga/Web/UrlTest.php @@ -82,13 +82,13 @@ class UrlTest extends BaseTestCase $url = Url::fromPath('/my/test/url.html'); $this->assertEquals( - '/', + '', $url->getBaseUrl(), 'Url::fromPath does not recognize the correct base url' ); $this->assertEquals( - 'my/test/url.html', - $url->getPath(), + '/my/test/url.html', + $url->getAbsoluteUrl(), 'Url::fromPath does not recognize the correct url path' ); } From 33231d24dab942b31c924c4c8e352dd73f80939b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 14:15:04 +0200 Subject: [PATCH 195/225] Url: Add support macros refs #5600 --- library/Icinga/Web/Url.php | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 6f39bbab7..617ac7a4e 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -142,7 +142,7 @@ class Url return $urlObject; } - $urlParts = parse_url($url); + $urlParts = parse_url(static::resolveUrlMacros($url)); if (isset($urlParts['scheme']) && $urlParts['scheme'] !== $request->getScheme()) { $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) @@ -189,6 +189,33 @@ class Url return $urlObject; } + /** + * Return the given url with all macros being resolved + * + * @param string $url + * + * @return string + */ + public static function resolveUrlMacros($url) + { + $serverName = static::getRequest()->getServer('SERVER_NAME'); + $macros = array( + 'HTTP_AUTHORITY' => 'http://' . $serverName, + 'HTTPS_AUTHORITY' => 'https://' . $serverName + ); + + if (preg_match_all('@\$([^\$\s]+)\$@', $url, $matches)) { + foreach ($matches[1] as $macroIndex => $macroName) { + if (isset($macros[$macroName])) { + $placeholder = $matches[0][$macroIndex]; + $url = str_replace($placeholder, $macros[$macroName], $url); + } + } + } + + return $url; + } + /** * Create a new filter that needs to fullfill the base filter and the optional filter (if it exists) * From b4411569347e4a945a768ae3f0c1f7c6cefe9eed Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 11:54:13 +0200 Subject: [PATCH 196/225] Implement IniParser::parseIniFile() refs #10150 --- library/Icinga/File/Ini/IniParser.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/library/Icinga/File/Ini/IniParser.php b/library/Icinga/File/Ini/IniParser.php index 43c96836d..b22841f78 100644 --- a/library/Icinga/File/Ini/IniParser.php +++ b/library/Icinga/File/Ini/IniParser.php @@ -9,6 +9,7 @@ use Icinga\File\Ini\Dom\Document; use Icinga\File\Ini\Dom\Directive; use Icinga\Application\Logger; use Icinga\Exception\ConfigurationError; +use Icinga\Exception\NotReadableError; class IniParser { @@ -239,4 +240,25 @@ class IniParser } return $doc; } + + /** + * Read the ini file and parse it with ::parseIni() + * + * @param string $file The ini file to read + * + * @return Document A mutable DOM object + * @throws NotReadableError When the file cannot be read + */ + public static function parseIniFile($file) + { + if (false === ($path = realpath($file))) { + throw new NotReadableError('couldn\'t compute the absolute path of `%s\'', $file); + } + + if (false === ($content = file_get_contents($path))) { + throw new NotReadableError('couldn\'t read the file `%s\'', $path); + } + + return self::parseIni($content); + } } From 777c4d0bafa7565b62b5f73d2a54973585d7646b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:03:19 +0200 Subject: [PATCH 197/225] Implement Section::toArray() refs #10150 --- library/Icinga/File/Ini/Dom/Section.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/Icinga/File/Ini/Dom/Section.php b/library/Icinga/File/Ini/Dom/Section.php index dbc9188cb..42dbac709 100644 --- a/library/Icinga/File/Ini/Dom/Section.php +++ b/library/Icinga/File/Ini/Dom/Section.php @@ -169,4 +169,18 @@ class Section $str = str_replace(';', '\\;', $str); return str_replace(PHP_EOL, ' ', $str); } + + /** + * Convert $this to an array + * + * @return array + */ + public function toArray() + { + $a = array(); + foreach ($this->directives as $directive) { + $a[$directive->getKey()] = $directive->getValue(); + } + return $a; + } } From acb93ce1ae4d761a2fead31aac0e1648efffb6db Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:13:10 +0200 Subject: [PATCH 198/225] Implement Document::toArray() refs #10150 --- library/Icinga/File/Ini/Dom/Document.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/Icinga/File/Ini/Dom/Document.php b/library/Icinga/File/Ini/Dom/Document.php index 69b628916..32f5b92c9 100644 --- a/library/Icinga/File/Ini/Dom/Document.php +++ b/library/Icinga/File/Ini/Dom/Document.php @@ -115,4 +115,18 @@ class Document } return $str; } + + /** + * Convert $this to an array + * + * @return array + */ + public function toArray() + { + $a = array(); + foreach ($this->sections as $section) { + $a[$section->getName()] = $section->toArray(); + } + return $a; + } } From 626c3494e48d600adc21fbfd33d4ffa2f17e7541 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:46:15 +0200 Subject: [PATCH 199/225] Config::fromIni(): use IniParser::parseIniFile() instead of parse_ini_file() refs #10150 --- library/Icinga/Application/Config.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index 0538f01d4..d7bfe64a6 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -12,6 +12,7 @@ use Icinga\Data\ConfigObject; use Icinga\Data\Selectable; use Icinga\Data\SimpleQuery; use Icinga\File\Ini\IniWriter; +use Icinga\File\Ini\IniParser; use Icinga\Exception\NotReadableError; /** @@ -313,7 +314,7 @@ class Config implements Countable, Iterator, Selectable if ($filepath === false) { $emptyConfig->setConfigFile($file); } elseif (is_readable($filepath)) { - $config = new static(new ConfigObject(parse_ini_file($filepath, true))); + $config = static::fromArray(IniParser::parseIniFile($filepath)->toArray()); $config->setConfigFile($filepath); return $config; } elseif (@file_exists($filepath)) { From b69311165c0c207451e12593610ab7e1ed8677dc Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 22 Sep 2015 14:53:29 +0200 Subject: [PATCH 200/225] Conform to coding guidelines --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 490fa44db..1d29e018d 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -534,10 +534,10 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken public function getMemberships(User $user) { if ($this->groupClass === 'posixGroup') { - # Posix group only uses simple user name + // Posix group only uses simple user name $userDn = $user->getUsername(); } else { - # LDAP groups use the complete DN + // LDAP groups use the complete DN if (($userDn = $user->getAdditional('ldap_dn')) === null) { $userQuery = $this->ds ->select() From 81e13109fc47444b568d5e0cf1b7bc295736117e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 15:51:00 +0200 Subject: [PATCH 201/225] Revert "Url: Add support macros" This reverts commit 33231d24dab942b31c924c4c8e352dd73f80939b. There is no need for url macros, since we're handling absolute urls correctly now. --- library/Icinga/Web/Url.php | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 617ac7a4e..6f39bbab7 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -142,7 +142,7 @@ class Url return $urlObject; } - $urlParts = parse_url(static::resolveUrlMacros($url)); + $urlParts = parse_url($url); if (isset($urlParts['scheme']) && $urlParts['scheme'] !== $request->getScheme()) { $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) @@ -189,33 +189,6 @@ class Url return $urlObject; } - /** - * Return the given url with all macros being resolved - * - * @param string $url - * - * @return string - */ - public static function resolveUrlMacros($url) - { - $serverName = static::getRequest()->getServer('SERVER_NAME'); - $macros = array( - 'HTTP_AUTHORITY' => 'http://' . $serverName, - 'HTTPS_AUTHORITY' => 'https://' . $serverName - ); - - if (preg_match_all('@\$([^\$\s]+)\$@', $url, $matches)) { - foreach ($matches[1] as $macroIndex => $macroName) { - if (isset($macros[$macroName])) { - $placeholder = $matches[0][$macroIndex]; - $url = str_replace($placeholder, $macros[$macroName], $url); - } - } - } - - return $url; - } - /** * Create a new filter that needs to fullfill the base filter and the optional filter (if it exists) * From 5193fce1dd14407d573376e7cfb69e598a494935 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 16:18:27 +0200 Subject: [PATCH 202/225] Url: Fix external url detection refs #5600 --- library/Icinga/Web/Url.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 6f39bbab7..56206287e 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -143,17 +143,15 @@ class Url } $urlParts = parse_url($url); - if (isset($urlParts['scheme']) && $urlParts['scheme'] !== $request->getScheme()) { + if (isset($urlParts['scheme']) && ( + $urlParts['scheme'] !== $request->getScheme() + || (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME')) + || (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT'))) + ) { $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) : ''); $urlObject->setIsExternal(); - } elseif ( - (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME')) - || (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')) - ) { - $baseUrl = $urlParts['host'] . (isset($urlParts['port']) ? (':' . $urlParts['port']) : ''); - $urlObject->setIsExternal(); } else { $baseUrl = ''; } From f47c0475c9b52f1c9863a425d6fbd12025bb31e9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 16:18:55 +0200 Subject: [PATCH 203/225] NavigationItemForm: Provide a helpful hint how to input external urls refs #5600 --- application/forms/Navigation/NavigationItemForm.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php index c7cb43422..0011b044c 100644 --- a/application/forms/Navigation/NavigationItemForm.php +++ b/application/forms/Navigation/NavigationItemForm.php @@ -52,7 +52,9 @@ class NavigationItemForm extends Form 'allowEmpty' => true, 'label' => $this->translate('Url'), 'description' => $this->translate( - 'The url of this navigation item. Leave blank if you only want the name being displayed' + 'The url of this navigation item. Leave blank if you only want the' + . ' name being displayed. For external urls, make sure to prepend' + . ' an appropriate protocol identifier (e.g. http://example.tld)' ) ) ); From fcdf41a9b80d72e3e08e1af696b5dc43492c457d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 22 Sep 2015 16:24:09 +0200 Subject: [PATCH 204/225] DashletForm: Provide a helpful hint how to input external urls refs #5600 --- application/forms/Navigation/DashletForm.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/DashletForm.php b/application/forms/Navigation/DashletForm.php index c1e6f66ec..8e065a52c 100644 --- a/application/forms/Navigation/DashletForm.php +++ b/application/forms/Navigation/DashletForm.php @@ -25,7 +25,10 @@ class DashletForm extends NavigationItemForm array( 'required' => true, 'label' => $this->translate('Url'), - 'description' => $this->translate('The url to load in the dashlet') + 'description' => $this->translate( + 'The url to load in the dashlet. For external urls, make sure to prepend' + . ' an appropriate protocol identifier (e.g. http://example.tld)' + ) ) ); } From 939188be9473a56e8d32ba9d6a37f516571180fa Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:00:46 +0200 Subject: [PATCH 205/225] FilterMatch: Do not re-invent the parents implementation of matches() refs #5600 --- library/Icinga/Data/Filter/FilterMatch.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/library/Icinga/Data/Filter/FilterMatch.php b/library/Icinga/Data/Filter/FilterMatch.php index a5c058b91..9f25ac88d 100644 --- a/library/Icinga/Data/Filter/FilterMatch.php +++ b/library/Icinga/Data/Filter/FilterMatch.php @@ -5,21 +5,4 @@ namespace Icinga\Data\Filter; class FilterMatch extends FilterExpression { - public function matches($row) - { - if (! isset($row->{$this->column})) { - // TODO: REALLY? Exception? - return false; - } - $expression = (string) $this->expression; - if (strpos($expression, '*') === false) { - return (string) $row->{$this->column} === $expression; - } else { - $parts = array(); - foreach (preg_split('/\*/', $expression) as $part) { - $parts[] = preg_quote($part); - } - return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); - } - } } From e4e560ab1aff05d0d96159057be6659874301a24 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:01:08 +0200 Subject: [PATCH 206/225] FilterMatchNot: Do not re-invent the parents implementation of matches() refs #5600 --- library/Icinga/Data/Filter/FilterMatchNot.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/library/Icinga/Data/Filter/FilterMatchNot.php b/library/Icinga/Data/Filter/FilterMatchNot.php index fe9fbb96a..a2a78a4ed 100644 --- a/library/Icinga/Data/Filter/FilterMatchNot.php +++ b/library/Icinga/Data/Filter/FilterMatchNot.php @@ -7,15 +7,6 @@ class FilterMatchNot extends FilterExpression { public function matches($row) { - $expression = (string) $this->expression; - if (strpos($expression, '*') === false) { - return (string) $row->{$this->column} !== $expression; - } else { - $parts = array(); - foreach (preg_split('/\*/', $expression) as $part) { - $parts[] = preg_quote($part); - } - return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); - } + return !parent::matches($row); } } From 9042d55dd4249b54acc7297b2e534c34ea435579 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:02:13 +0200 Subject: [PATCH 207/225] FilterExpression: Add support multi value columns in method matches() refs #5600 --- .../Icinga/Data/Filter/FilterExpression.php | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/library/Icinga/Data/Filter/FilterExpression.php b/library/Icinga/Data/Filter/FilterExpression.php index 8ef1e5076..ca5866a6a 100644 --- a/library/Icinga/Data/Filter/FilterExpression.php +++ b/library/Icinga/Data/Filter/FilterExpression.php @@ -97,18 +97,41 @@ class FilterExpression extends Filter public function matches($row) { + if (! isset($row->{$this->column})) { + // TODO: REALLY? Exception? + return false; + } + if (is_array($this->expression)) { return in_array($row->{$this->column}, $this->expression); - } elseif (strpos($this->expression, '*') === false) { - return (string) $row->{$this->column} === (string) $this->expression; - } else { - $parts = preg_split('~\*~', $this->expression); - foreach ($parts as & $part) { - $part = preg_quote($part); - } - $pattern = '/^' . implode('.*', $parts) . '$/'; - return (bool) preg_match($pattern, $row->{$this->column}); } + + $expression = (string) $this->expression; + if (strpos($expression, '*') === false) { + if (is_array($row->{$this->column})) { + return in_array($expression, $row->{$this->column}); + } + + return (string) $row->{$this->column} === $expression; + } + + $parts = array(); + foreach (preg_split('~\*~', $expression) as $part) { + $parts[] = preg_quote($part); + } + $pattern = '/^' . implode('.*', $parts) . '$/'; + + if (is_array($row->{$this->column})) { + foreach ($row->{$this->column} as $candidate) { + if (preg_match($pattern, $candidate)) { + return true; + } + } + + return false; + } + + return (bool) preg_match($pattern, $row->{$this->column}); } public function andFilter(Filter $filter) From e42548fc80488aba0ebb5ea9cfccc3be7002277d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:02:43 +0200 Subject: [PATCH 208/225] MonitoredObject: Allow to fetch servicegroups for host objects refs #5600 --- .../library/Monitoring/Object/MonitoredObject.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 76e666d89..d4823892c 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -565,12 +565,15 @@ abstract class MonitoredObject implements Filterable */ public function fetchServicegroups() { - $this->servicegroups = $this->backend->select() + $query = $this->backend->select() ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias')) - ->where('host_name', $this->host_name) - ->where('service_description', $this->service_description) - ->applyFilter($this->getFilter()) - ->fetchPairs(); + ->where('host_name', $this->host_name); + + if ($this->type === self::TYPE_SERVICE) { + $query->where('service_description', $this->service_description); + } + + $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs(); return $this; } From 40c2cb2d12c3013817b48b2d5a8a1e5825c553c0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:03:41 +0200 Subject: [PATCH 209/225] MonitoredObject: Support host- and servicegroup filters in method matches() refs #5600 --- .../Monitoring/Object/MonitoredObject.php | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index d4823892c..98ada34de 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -229,16 +229,35 @@ abstract class MonitoredObject implements Filterable ); } + $queryString = $filter->toQueryString(); $row = clone $this->properties; - if ($this->customvars === null) { - $this->fetchCustomvars(); + if (strpos($queryString, '_host_') !== false || strpos($queryString, '_service_') !== false) { + if ($this->customvars === null) { + $this->fetchCustomvars(); + } + + foreach ($this->customvars as $name => $value) { + if (! is_object($value)) { + $row->{'_' . $this->getType() . '_' . strtolower(str_replace(' ', '_', $name))} = $value; + } + } } - foreach ($this->customvars as $name => $value) { - if (! is_object($value)) { - $row->{'_' . $this->getType() . '_' . strtolower(str_replace(' ', '_', $name))} = $value; + if (strpos($queryString, 'hostgroup_name') !== false) { + if ($this->hostgroups === null) { + $this->fetchHostgroups(); } + + $row->hostgroup_name = array_keys($this->hostgroups); + } + + if (strpos($queryString, 'servicegroup_name') !== false) { + if ($this->servicegroups === null) { + $this->fetchServicegroups(); + } + + $row->servicegroup_name = array_keys($this->servicegroups); } return $filter->matches($row); From 6aecfe69591c51a91f9f8e389aa2644a5b71a75b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 13:45:33 +0200 Subject: [PATCH 210/225] Introduce base form class ActionForm refs #5600 --- .../forms/Navigation/ActionForm.php | 75 +++++++++++++++++++ .../forms/Navigation/HostActionForm.php | 24 +----- .../forms/Navigation/ServiceActionForm.php | 24 +----- 3 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 modules/monitoring/application/forms/Navigation/ActionForm.php diff --git a/modules/monitoring/application/forms/Navigation/ActionForm.php b/modules/monitoring/application/forms/Navigation/ActionForm.php new file mode 100644 index 000000000..e712b254d --- /dev/null +++ b/modules/monitoring/application/forms/Navigation/ActionForm.php @@ -0,0 +1,75 @@ +addElement( + 'text', + 'filter', + array( + 'allowEmpty' => true, + 'label' => $this->translate('Filter'), + 'description' => $this->translate( + 'Display this action only for objects matching this filter. Leave it blank' + . ' if you want this action being displayed regardless of the object' + ) + ) + ); + } + + /** + * {@inheritdoc} + */ + public function isValid($formData) + { + if (! parent::isValid($formData)) { + return false; + } + + if (($filterString = $this->getValue('filter')) !== null) { + $filter = Filter::matchAll(); + $filter->setAllowedFilterColumns(array( + 'host_name', + 'hostgroup_name', + 'instance_name', + 'service_description', + 'servicegroup_name', + function ($c) { + return preg_match('/^_(?:host|service)_/', $c); + } + )); + + try { + $filter->addFilter(Filter::fromQueryString($filterString)); + } catch (QueryException $_) { + $this->getElement('filter')->addError(sprintf( + $this->translate('Invalid filter provided. You can only use the following columns: %s'), + implode(', ', array( + 'instance_name', + 'host_name', + 'hostgroup_name', + 'service_description', + 'servicegroup_name', + '_(host|service)_' + )) + )); + return false; + } + } + + return true; + } +} diff --git a/modules/monitoring/application/forms/Navigation/HostActionForm.php b/modules/monitoring/application/forms/Navigation/HostActionForm.php index 726d740d0..2e8bf32cc 100644 --- a/modules/monitoring/application/forms/Navigation/HostActionForm.php +++ b/modules/monitoring/application/forms/Navigation/HostActionForm.php @@ -3,28 +3,6 @@ namespace Icinga\Module\Monitoring\Forms\Navigation; -use Icinga\Forms\Navigation\NavigationItemForm; - -class HostActionForm extends NavigationItemForm +class HostActionForm extends ActionForm { - /** - * {@inheritdoc} - */ - public function createElements(array $formData) - { - parent::createElements($formData); - - $this->addElement( - 'text', - 'filter', - array( - 'allowEmpty' => true, - 'label' => $this->translate('Filter'), - 'description' => $this->translate( - 'Display this action only for hosts matching this filter. Leave' - . ' blank if you want this action being displayed for all hosts' - ) - ) - ); - } } diff --git a/modules/monitoring/application/forms/Navigation/ServiceActionForm.php b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php index 6be89b0a3..3c9b37d5a 100644 --- a/modules/monitoring/application/forms/Navigation/ServiceActionForm.php +++ b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php @@ -3,28 +3,6 @@ namespace Icinga\Module\Monitoring\Forms\Navigation; -use Icinga\Forms\Navigation\NavigationItemForm; - -class ServiceActionForm extends NavigationItemForm +class ServiceActionForm extends ActionForm { - /** - * {@inheritdoc} - */ - public function createElements(array $formData) - { - parent::createElements($formData); - - $this->addElement( - 'text', - 'filter', - array( - 'allowEmpty' => true, - 'label' => $this->translate('Filter'), - 'description' => $this->translate( - 'Display this action only for services matching this filter. Leave' - . ' blank if you want this action being displayed for all services' - ) - ) - ); - } } From 4d303e7121678745a6ec26c31d78c8666fca8f63 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 23 Sep 2015 14:21:04 +0200 Subject: [PATCH 211/225] NavigationController: Redirect to the shared overview if its the referrer.. ..when editing a navigation item. refs #5600 --- application/controllers/NavigationController.php | 3 ++- application/views/scripts/navigation/shared.phtml | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 335b05adc..2519647b2 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -188,9 +188,10 @@ class NavigationController extends Controller public function editAction() { $itemName = $this->params->getRequired('name'); + $referrer = $this->params->get('referrer', 'index'); $form = new NavigationConfigForm(); - $form->setRedirectUrl('navigation'); + $form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation'); $form->setItemTypes($this->listItemTypes()); $form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName)); $form->setUser($this->Auth()->getUser()); diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml index 7f148e3e3..939249f84 100644 --- a/application/views/scripts/navigation/shared.phtml +++ b/application/views/scripts/navigation/shared.phtml @@ -24,7 +24,10 @@
        qlink( $name, 'navigation/edit', - array('name' => $name), + array( + 'name' => $name, + 'referrer' => 'shared' + ), array( 'title' => sprintf($this->translate('Edit shared navigation item %s'), $name) ) From 338c0680e050d71e97ea10d872ab965922e6a5f1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 12:58:11 +0200 Subject: [PATCH 212/225] NavigationConfigForm: Consider a user's config and the share config as one Actually, we need a more sophisticated solution for this, but I guess this is a proper quickfix to avoid any conflicts when switching between shared and non-shared. refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 4f4f3a975..7316b005f 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -292,7 +292,7 @@ class NavigationConfigForm extends ConfigForm } $itemName = $data['name']; - if ($config->hasSection($itemName)) { + if ($config->hasSection($itemName) || $this->getUserConfig()->hasSection($itemName)) { throw new IcingaException( $this->translate('A navigation item with the name "%s" does already exist'), $itemName @@ -313,7 +313,8 @@ class NavigationConfigForm extends ConfigForm * * @return $this * - * @throws NotFoundError In case no navigation item with the given name is found + * @throws NotFoundError In case no navigation item with the given name is found + * @throws IcingaException In case a navigation item with the same name already exists */ public function edit($name, array $data) { @@ -357,6 +358,13 @@ class NavigationConfigForm extends ConfigForm if ($data['name'] !== $name) { $oldName = $name; $name = $data['name']; + + if ($config->hasSection($name) || $this->getUserConfig()->hasSection($name)) { + throw new IcingaException( + $this->translate('A navigation item with the name "%s" does already exist'), + $name + ); + } } unset($data['name']); From 165dc00fadb221bb36713ec0c95f2e4fc6f32660 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 14:03:15 +0200 Subject: [PATCH 213/225] Url: Do not throw ProgrammingError if there is no relative url available refs #5600 --- library/Icinga/Web/Url.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php index 56206287e..aa7f652a3 100644 --- a/library/Icinga/Web/Url.php +++ b/library/Icinga/Web/Url.php @@ -282,19 +282,15 @@ class Url * Return the relative url * * @return string - * - * @throws ProgrammingError In case no relative url path is set or it is absolute */ public function getRelativeUrl($separator = '&') { - $path = $this->getPath(); - if (! $path) { - throw new ProgrammingError('Unable to provide a relative URL. No path set'); - } elseif ($path[0] === '/') { - throw new ProgrammingError('Cannot provide a relative URL. Path is absolute'); + $path = $this->buildPathQueryAndFragment($separator); + if ($path && $path[0] === '/') { + return ''; } - return $this->buildPathQueryAndFragment($separator); + return $path; } /** From c4df7e05beb22ac3d6ff6642f05840645987eb43 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 14:05:28 +0200 Subject: [PATCH 214/225] NavigationController: Rerender the layout when adding a menu entry refs #5600 --- application/controllers/NavigationController.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 2519647b2..0a79c5468 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -162,14 +162,20 @@ class NavigationController extends Controller $form->setUser($this->Auth()->getUser()); $form->setShareConfig(Config::app('navigation')); $form->setOnSuccess(function (NavigationConfigForm $form) { + $data = array_filter($form->getValues()); + try { - $form->add(array_filter($form->getValues())); + $form->add($data); } catch (Exception $e) { $form->error($e->getMessage()); return false; } if ($form->save()) { + if (isset($data['type']) && $data['type'] === 'menu-item') { + $form->getResponse()->setRerenderLayout(); + } + Notification::success(t('Navigation item successfully created')); return true; } From b7ef951e57b463d0f67c7d943a7efba4567968d9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 14:05:57 +0200 Subject: [PATCH 215/225] NavigationController: Rerender the layout when editing a menu entry refs #5600 --- .../controllers/NavigationController.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 0a79c5468..57c6cea25 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -203,13 +203,15 @@ class NavigationController extends Controller $form->setUser($this->Auth()->getUser()); $form->setShareConfig(Config::app('navigation')); $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) { + $data = array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + ); + try { - $form->edit($itemName, array_map( - function ($v) { - return $v !== '' ? $v : null; - }, - $form->getValues() - )); + $form->edit($itemName, $data); } catch (NotFoundError $e) { throw $e; } catch (Exception $e) { @@ -218,6 +220,10 @@ class NavigationController extends Controller } if ($form->save()) { + if (isset($data['type']) && $data['type'] === 'menu-item') { + $form->getResponse()->setRerenderLayout(); + } + Notification::success(sprintf(t('Navigation item "%s" successfully updated'), $itemName)); return true; } From cd1510d846715be5eac7b89ea65e5657e32bcb3e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 15:49:04 +0200 Subject: [PATCH 216/225] NavigationConfigForm: Set a "name" as default key column for nav configs refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index 7316b005f..dab5b5e76 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -114,6 +114,7 @@ class NavigationConfigForm extends ConfigForm */ public function setUserConfig(Config $config) { + $config->getConfigObject()->setKeyColumn('name'); $this->userConfig = $config; return $this; } @@ -126,7 +127,7 @@ class NavigationConfigForm extends ConfigForm public function getUserConfig() { if ($this->userConfig === null) { - $this->userConfig = $this->getUser()->loadNavigationConfig(); + $this->setUserConfig($this->getUser()->loadNavigationConfig()); } return $this->userConfig; @@ -141,6 +142,7 @@ class NavigationConfigForm extends ConfigForm */ public function setShareConfig(Config $config) { + $config->getConfigObject()->setKeyColumn('name'); $this->shareConfig = $config; return $this; } From 3f22469c1857ca7e07e5348c79961f4cbaf50fd5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 15:50:19 +0200 Subject: [PATCH 217/225] NavigationConfigForm: Fix name validation when adding or editing items refs #5600 --- .../forms/Navigation/NavigationConfigForm.php | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index dab5b5e76..d2bd91d3c 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -279,11 +279,13 @@ class NavigationConfigForm extends ConfigForm throw new InvalidArgumentException('Key \'name\' missing'); } + $shared = false; $config = $this->getUserConfig(); if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ($this->getUser()->can('application/share/navigation')) { $data['owner'] = $this->getUser()->getUsername(); $config = $this->getShareConfig(); + $shared = true; } else { unset($data['users']); unset($data['groups']); @@ -291,10 +293,24 @@ class NavigationConfigForm extends ConfigForm } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) { $data['owner'] = $this->getUser()->getUsername(); $config = $this->getShareConfig(); + $shared = true; } $itemName = $data['name']; - if ($config->hasSection($itemName) || $this->getUserConfig()->hasSection($itemName)) { + $exists = $config->hasSection($itemName); + if (! $exists) { + if ($shared) { + $exists = $this->getUserConfig()->hasSection($itemName); + } else { + $exists = (bool) $this->getShareConfig() + ->select() + ->where('name', $itemName) + ->where('owner', $this->getUser()->getUsername()) + ->count(); + } + } + + if ($exists) { throw new IcingaException( $this->translate('A navigation item with the name "%s" does already exist'), $itemName @@ -361,7 +377,26 @@ class NavigationConfigForm extends ConfigForm $oldName = $name; $name = $data['name']; - if ($config->hasSection($name) || $this->getUserConfig()->hasSection($name)) { + $exists = $config->hasSection($name); + if (! $exists) { + $ownerName = $itemConfig->owner ?: $this->getUser()->getUsername(); + if ($shared || $this->hasBeenShared($oldName)) { + if ($ownerName === $this->getUser()->getUsername()) { + $exists = $this->getUserConfig()->hasSection($name); + } else { + $owner = new User($ownerName); + $exists = $owner->loadNavigationConfig()->hasSection($name); + } + } else { + $exists = (bool) $this->getShareConfig() + ->select() + ->where('name', $name) + ->where('owner', $ownerName) + ->count(); + } + } + + if ($exists) { throw new IcingaException( $this->translate('A navigation item with the name "%s" does already exist'), $name From e8f93e8ea1648faa36e0c708f75e0a4d58719033 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 24 Sep 2015 15:50:40 +0200 Subject: [PATCH 218/225] NavigationConfigForm: Translate non-translated error messages refs #5600 --- application/forms/Navigation/NavigationConfigForm.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index d2bd91d3c..ad58ac4a1 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -464,7 +464,10 @@ class NavigationConfigForm extends ConfigForm $children = $this->getFlattenedChildren($name); if (! empty($children)) { throw new IcingaException( - 'Unable to delete navigation item "%s". There are other items dependent from it: %s', + $this->translate( + 'Unable to delete navigation item "%s". There' + . ' are other items dependent from it: %s' + ), $name, join(', ', $children) ); @@ -500,8 +503,10 @@ class NavigationConfigForm extends ConfigForm if ($parent && $this->hasBeenShared($parent)) { throw new IcingaException( - 'Unable to unshare navigation item "%s". It is dependent from item "%s".' - . ' Dependent items can only be unshared by unsharing their parent', + $this->translate( + 'Unable to unshare navigation item "%s". It is dependent from item "%s".' + . ' Dependent items can only be unshared by unsharing their parent' + ), $name, $parent ); From 2952eaefcd5773ecada25ca21f5223c029d21979 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 25 Sep 2015 10:51:16 +0200 Subject: [PATCH 219/225] NavigationController: Rerender the layout when unsharing menu entries refs #5600 --- application/controllers/NavigationController.php | 8 +++++++- application/forms/Navigation/NavigationConfigForm.php | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 57c6cea25..96d6cf7f8 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -293,9 +293,15 @@ class NavigationController extends Controller $form = new Form(array( 'onSuccess' => function ($form) use ($navigationConfigForm) { + $itemName = $form->getValue('name'); + try { - $navigationConfigForm->unshare($form->getValue('name')); + $newConfig = $navigationConfigForm->unshare($itemName); if ($navigationConfigForm->save()) { + if ($newConfig->getSection($itemName)->type === 'menu-item') { + $form->getResponse()->setRerenderLayout(); + } + Notification::success(sprintf( t('Navigation item "%s" has been unshared'), $form->getValue('name') diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index ad58ac4a1..b1b3aa058 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -350,7 +350,7 @@ class NavigationConfigForm extends ConfigForm : ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups'])) ) { // It is shared but shouldn't anymore - $config = $this->unshare($name, isset($data['parent']) ? $data['parent'] : null)->config; + $config = $this->unshare($name, isset($data['parent']) ? $data['parent'] : null); } } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ($this->getUser()->can('application/share/navigation')) { @@ -484,7 +484,7 @@ class NavigationConfigForm extends ConfigForm * @param string $name * @param string $parent * - * @return $this + * @return Config The new config of the given navigation item * * @throws NotFoundError In case no navigation item with the given name is found * @throws IcingaException In case the navigation item has a parent assigned to it @@ -536,7 +536,7 @@ class NavigationConfigForm extends ConfigForm $config->setSection($name, $itemConfig); $this->setIniConfig($config); - return $this; + return $config; } /** From 086579b2545525f642eaac3756959d4abb5d9af4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 25 Sep 2015 10:51:44 +0200 Subject: [PATCH 220/225] NavigationController: Rerender the layout when removing menu entries refs #5600 --- application/controllers/NavigationController.php | 6 +++++- application/forms/Navigation/NavigationConfigForm.php | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index 96d6cf7f8..f5c1d785a 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -257,7 +257,7 @@ class NavigationController extends Controller $form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $itemName)); $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) { try { - $navigationConfigForm->delete($itemName); + $itemConfig = $navigationConfigForm->delete($itemName); } catch (NotFoundError $e) { Notification::success(sprintf(t('Navigation Item "%s" not found. No action required'), $itemName)); return true; @@ -267,6 +267,10 @@ class NavigationController extends Controller } if ($navigationConfigForm->save()) { + if ($itemConfig->type === 'menu-item') { + $form->getResponse()->setRerenderLayout(); + } + Notification::success(sprintf(t('Navigation Item "%s" successfully removed'), $itemName)); return true; } diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php index b1b3aa058..ba1f20d30 100644 --- a/application/forms/Navigation/NavigationConfigForm.php +++ b/application/forms/Navigation/NavigationConfigForm.php @@ -8,6 +8,7 @@ use Icinga\Application\Config; use Icinga\Application\Logger; use Icinga\Application\Icinga; use Icinga\Authentication\Auth; +use Icinga\Data\ConfigObject; use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Exception\ProgrammingError; @@ -449,7 +450,7 @@ class NavigationConfigForm extends ConfigForm * * @param string $name * - * @return $this + * @return ConfigObject The navigation item's config * * @throws NotFoundError In case no navigation item with the given name is found * @throws IcingaException In case the navigation item has still children @@ -473,9 +474,10 @@ class NavigationConfigForm extends ConfigForm ); } + $section = $config->getSection($name); $config->removeSection($name); $this->setIniConfig($config); - return $this; + return $section; } /** From a090907373f24efbf71c9d19f3978270e522f48a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:46:15 +0200 Subject: [PATCH 221/225] Conform to coding guidelines --- library/Icinga/File/Ini/IniParser.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/Icinga/File/Ini/IniParser.php b/library/Icinga/File/Ini/IniParser.php index b22841f78..35e708594 100644 --- a/library/Icinga/File/Ini/IniParser.php +++ b/library/Icinga/File/Ini/IniParser.php @@ -251,12 +251,12 @@ class IniParser */ public static function parseIniFile($file) { - if (false === ($path = realpath($file))) { - throw new NotReadableError('couldn\'t compute the absolute path of `%s\'', $file); + if (($path = realpath($file)) === false) { + throw new NotReadableError('Couldn\'t compute the absolute path of `%s\'', $file); } - if (false === ($content = file_get_contents($path))) { - throw new NotReadableError('couldn\'t read the file `%s\'', $path); + if (($content = file_get_contents($path)) === false) { + throw new NotReadableError('Couldn\'t read the file `%s\'', $path); } return self::parseIni($content); From 8dc9928cb374dd70016409f6fc527c3003d127e9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:46:15 +0200 Subject: [PATCH 222/225] IniParser::parseIniFile(): return a Config instance refs #10150 --- library/Icinga/Application/Config.php | 4 +--- library/Icinga/File/Ini/IniParser.php | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index d7bfe64a6..bfe2ddd60 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -314,9 +314,7 @@ class Config implements Countable, Iterator, Selectable if ($filepath === false) { $emptyConfig->setConfigFile($file); } elseif (is_readable($filepath)) { - $config = static::fromArray(IniParser::parseIniFile($filepath)->toArray()); - $config->setConfigFile($filepath); - return $config; + return IniParser::parseIniFile($filepath); } elseif (@file_exists($filepath)) { throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath); } diff --git a/library/Icinga/File/Ini/IniParser.php b/library/Icinga/File/Ini/IniParser.php index 35e708594..72925cac1 100644 --- a/library/Icinga/File/Ini/IniParser.php +++ b/library/Icinga/File/Ini/IniParser.php @@ -10,6 +10,7 @@ use Icinga\File\Ini\Dom\Directive; use Icinga\Application\Logger; use Icinga\Exception\ConfigurationError; use Icinga\Exception\NotReadableError; +use Icinga\Application\Config; class IniParser { @@ -246,7 +247,7 @@ class IniParser * * @param string $file The ini file to read * - * @return Document A mutable DOM object + * @return Config * @throws NotReadableError When the file cannot be read */ public static function parseIniFile($file) @@ -259,6 +260,6 @@ class IniParser throw new NotReadableError('Couldn\'t read the file `%s\'', $path); } - return self::parseIni($content); + return Config::fromArray(self::parseIni($content)->toArray())->setConfigFile($file); } } From 812545c04bbc57ea42d75add1d9f85c1a27cab5a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Sep 2015 14:46:15 +0200 Subject: [PATCH 223/225] IniStore::load(): use IniParser::parseIniFile() instead of parse_ini_file() refs #10150 --- library/Icinga/User/Preferences/Store/IniStore.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php index 88fa7edec..e5dbe4286 100644 --- a/library/Icinga/User/Preferences/Store/IniStore.php +++ b/library/Icinga/User/Preferences/Store/IniStore.php @@ -8,6 +8,7 @@ use Icinga\Exception\NotReadableError; use Icinga\Exception\NotWritableError; use Icinga\User\Preferences; use Icinga\User\Preferences\PreferencesStore; +use Icinga\File\Ini\IniParser; /** * Load and save user preferences from and to INI files @@ -57,7 +58,7 @@ class IniStore extends PreferencesStore $this->getUser()->getUsername() ); } else { - $this->preferences = parse_ini_file($this->preferencesFile, true); + $this->preferences = IniParser::parseIniFile($this->preferencesFile)->toArray(); } } From 67a28a4b4723f4105b324e1ccc0294e1bebe6cda Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 25 Sep 2015 13:58:52 +0200 Subject: [PATCH 224/225] js: Do not re-open the detail url if it's the origin of a redirect refs #5600 --- public/js/icinga/loader.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 7d67acd95..7ea4575ac 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -331,7 +331,7 @@ } } - this.redirectToUrl(redirect, req.$target, req.getResponseHeader('X-Icinga-Rerender-Layout')); + this.redirectToUrl(redirect, req.$target, req.url, req.getResponseHeader('X-Icinga-Rerender-Layout')); return true; }, @@ -340,9 +340,10 @@ * * @param {string} url * @param {object} $target + * @param {string] origin * @param {boolean} rerenderLayout */ - redirectToUrl: function (url, $target, rerenderLayout) { + redirectToUrl: function (url, $target, origin, rerenderLayout) { var icinga = this.icinga; if (typeof rerenderLayout === 'undefined') { @@ -365,7 +366,15 @@ // Retain detail URL if the layout is rerendered parts = document.location.hash.split('#!').splice(1); if (parts.length) { - r.loadNext = parts; + r.loadNext = $.grep(parts, function (url) { + if (url !== origin) { + icinga.logger.debug('Retaining detail url ' + url); + return true; + } + + icinga.logger.debug('Discarding detail url ' + url + ' as it\'s the origin of the redirect'); + return false; + }); } } } else { @@ -609,7 +618,7 @@ this.processRedirectHeader(req); - if (typeof req.loadNext !== 'undefined') { + if (typeof req.loadNext !== 'undefined' && req.loadNext.length) { if ($('#col2').length) { this.loadUrl(req.loadNext[0], $('#col2')); this.icinga.ui.layout2col(); From f17c185aeddb7a14669b010a08977b07a394f53e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 25 Sep 2015 14:08:10 +0200 Subject: [PATCH 225/225] js: Improve logging when pushing history states --- public/js/icinga/history.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/js/icinga/history.js b/public/js/icinga/history.js index 676660531..7d044f405 100644 --- a/public/js/icinga/history.js +++ b/public/js/icinga/history.js @@ -57,15 +57,11 @@ * TODO: How should we handle POST requests? e.g. search VS login */ pushCurrentState: function () { - - var icinga = this.icinga; - // No history API, no action - if (!this.enabled) { + if (! this.enabled) { return; } - icinga.logger.debug('Pushing current state to history'); var url = ''; // We only store URLs of containers sitting directly under #main: @@ -85,6 +81,7 @@ // Did we find any URL? Then push it! if (url !== '') { + this.icinga.logger.debug('Pushing current state to history'); this.push(url); } }, @@ -112,6 +109,9 @@ push: function (url) { url = url.replace(/[\?&]?_(render|reload)=[a-z0-9]+/g, ''); if (this.lastPushUrl === url) { + this.icinga.logger.debug( + 'Ignoring history state push for url ' + url + ' as it\' currently on top of the stack' + ); return; } this.lastPushUrl = url;