summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Web/Navigation/NavigationItem.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Web/Navigation/NavigationItem.php')
-rw-r--r--library/Icinga/Web/Navigation/NavigationItem.php948
1 files changed, 948 insertions, 0 deletions
diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php
new file mode 100644
index 0000000..8aaf7b8
--- /dev/null
+++ b/library/Icinga/Web/Navigation/NavigationItem.php
@@ -0,0 +1,948 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+use Exception;
+use Icinga\Authentication\Auth;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
+use Icinga\Web\Url;
+use Traversable;
+
+/**
+ * A navigation item
+ */
+class NavigationItem implements IteratorAggregate
+{
+ /**
+ * Alternative markup element for items without a url
+ *
+ * @var string
+ */
+ 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
+ *
+ * @var bool
+ */
+ protected $active;
+
+ /**
+ * Whether this item is selected
+ *
+ * @var bool
+ */
+ protected $selected;
+
+ /**
+ * The CSS class used for the outer li element
+ *
+ * @var string
+ */
+ protected $cssClass;
+
+ /**
+ * 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
+ *
+ * @var array
+ */
+ protected $attributes;
+
+ /**
+ * This item's children
+ *
+ * @var Navigation
+ */
+ protected $children;
+
+ /**
+ * This item's icon
+ *
+ * @var string
+ */
+ protected $icon;
+
+ /**
+ * This item's name
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * This item's label
+ *
+ * @var string
+ */
+ protected $label;
+
+ /**
+ * The item's description
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * This item's parent
+ *
+ * @var NavigationItem
+ */
+ protected $parent;
+
+ /**
+ * This item's url
+ *
+ * @var Url
+ */
+ protected $url;
+
+ /**
+ * This item's url target
+ *
+ * @var string
+ */
+ protected $target;
+
+ /**
+ * Additional parameters for this item's url
+ *
+ * @var array
+ */
+ protected $urlParameters;
+
+ /**
+ * This item's renderer
+ *
+ * @var NavigationItemRenderer
+ */
+ protected $renderer;
+
+ /**
+ * Whether to render this item
+ *
+ * @var bool
+ */
+ protected $render;
+
+ /**
+ * Create a new NavigationItem
+ *
+ * @param string $name
+ * @param array $properties
+ */
+ public function __construct($name, array $properties = null)
+ {
+ $this->setName($name);
+ $this->children = new Navigation();
+
+ if (! empty($properties)) {
+ $this->setProperties($properties);
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Initialize this NavigationItem
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * @return Navigation
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->getChildren();
+ }
+
+ /**
+ * Return whether this item is active
+ *
+ * @return bool
+ */
+ public function getActive()
+ {
+ 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;
+ }
+
+ /**
+ * Set whether this item is active
+ *
+ * If it's active and has a parent, the parent gets activated as well.
+ *
+ * @param bool $active
+ *
+ * @return $this
+ */
+ public function setActive($active = true)
+ {
+ $this->active = (bool) $active;
+ if ($this->active && $this->getParent() !== null) {
+ $this->getParent()->setActive();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether this item is selected
+ *
+ * @return bool
+ */
+ public function getSelected()
+ {
+ if ($this->selected === null) {
+ $this->active = false;
+ if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) {
+ $this->setSelected();
+ }
+ }
+
+ return $this->selected;
+ }
+
+ /**
+ * Set whether this item is active
+ *
+ * If it's active and has a parent, the parent gets activated as well.
+ *
+ * @param bool $selected
+ *
+ * @return $this
+ */
+ public function setSelected($selected = true)
+ {
+ $this->selected = (bool) $selected;
+
+ return $this;
+ }
+
+ /**
+ * Get the CSS class used for the outer li element
+ *
+ * @return string
+ */
+ public function getCssClass()
+ {
+ return $this->cssClass;
+ }
+
+ /**
+ * Set the CSS class to use for the outer li element
+ *
+ * @param string $class
+ *
+ * @return $this
+ */
+ public function setCssClass($class)
+ {
+ $this->cssClass = (string) $class;
+ return $this;
+ }
+
+ /**
+ * Return this item's priority
+ *
+ * @return int
+ */
+ public function getPriority()
+ {
+ return $this->priority !== null ? $this->priority : 100;
+ }
+
+ /**
+ * 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
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ $attributes = $this->getAttributes();
+ return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+ }
+
+ /**
+ * Set the value of the given element attribute
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Return the attributes of this item's element
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes ?: array();
+ }
+
+ /**
+ * Set the attributes of this item's element
+ *
+ * @param array $attributes
+ *
+ * @return $this
+ */
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+ return $this;
+ }
+
+ /**
+ * Add a child to this item
+ *
+ * If the child is active this item gets activated as well.
+ *
+ * @param NavigationItem $child
+ *
+ * @return $this
+ */
+ public function addChild(NavigationItem $child)
+ {
+ $this->getChildren()->addItem($child->setParent($this));
+ if ($child->getActive()) {
+ $this->setActive();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return this item's children
+ *
+ * @return Navigation
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Return whether this item has any children
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return ! $this->getChildren()->isEmpty();
+ }
+
+ /**
+ * Set this item's children
+ *
+ * @param array|Navigation $children
+ *
+ * @return $this
+ */
+ 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');
+ }
+
+ foreach ($children as $item) {
+ $item->setParent($this);
+ }
+
+ $this->children = $children;
+ return $this;
+ }
+
+ /**
+ * Return this item's icon
+ *
+ * @return string
+ */
+ public function getIcon()
+ {
+ return $this->icon;
+ }
+
+ /**
+ * Set this item's icon
+ *
+ * @param string $icon
+ *
+ * @return $this
+ */
+ public function setIcon($icon)
+ {
+ $this->icon = $icon;
+ 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()->getUniqueName() . '-' . $this->getEscapedName();
+ }
+
+ /**
+ * Return this item's name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set this item's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ 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
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->label !== null ? $this->label : $this->getName();
+ }
+
+ /**
+ * Set this item's label
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setLabel($label)
+ {
+ $this->label = $label;
+ return $this;
+ }
+
+ /**
+ * Get the item's description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set the item's description
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ 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
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ if ($this->url === null && $this->hasChildren()) {
+ $this->setUrl(Url::fromPath('navigation/dashboard', array('name' => strtolower($this->getName()))));
+ }
+
+ return $this->url;
+ }
+
+ /**
+ * Set this item's 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($this->resolveMacros($url));
+ } elseif ($url instanceof Url) {
+ $url = Url::fromPath($this->resolveMacros($url->getAbsoluteUrl()));
+ } else {
+ throw new InvalidArgumentException('Argument $url must be of type string or Url');
+ }
+
+ $this->url = $url;
+
+ 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
+ *
+ * @return array
+ */
+ public function getUrlParameters()
+ {
+ return $this->urlParameters ?: array();
+ }
+
+ /**
+ * Set additional parameters for this item's url
+ *
+ * @param array $urlParameters
+ *
+ * @return $this
+ */
+ public function setUrlParameters(array $urlParameters)
+ {
+ $this->urlParameters = $urlParameters;
+ return $this;
+ }
+
+ /**
+ * Set this item's properties
+ *
+ * Unknown properties (no matching setter) are considered as element attributes.
+ *
+ * @param array $properties
+ *
+ * @return $this
+ */
+ public function setProperties(array $properties)
+ {
+ foreach ($properties as $name => $value) {
+ $setter = 'set' . ucfirst($name);
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ } else {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ 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 ($this->priority === null) {
+ $priority = $item->getPriority();
+ if ($priority !== 100) {
+ $this->setPriority($priority);
+ }
+ }
+
+ if (! $this->getIcon()) {
+ $this->setIcon($item->getIcon());
+ }
+
+ if ($this->getLabel() === $this->getName() && $item->getLabel() !== $item->getName()) {
+ $this->setLabel($item->getLabel());
+ }
+
+ if ($this->target === null && ($target = $item->getTarget()) !== null) {
+ $this->setTarget($target);
+ }
+
+ if ($this->renderer === null) {
+ $renderer = $item->getRenderer();
+ if (get_class($renderer) !== 'NavigationItemRenderer') {
+ $this->setRenderer($renderer);
+ }
+ }
+
+ 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 $this;
+ }
+
+ /**
+ * Return whether it's possible to merge this item with the given one
+ *
+ * @param NavigationItem $item
+ *
+ * @return bool
+ */
+ public function conflictsWith(NavigationItem $item)
+ {
+ if (! $item instanceof $this) {
+ return true;
+ }
+
+ if ($this->getUrl() === null || $item->getUrl() === null) {
+ return false;
+ }
+
+ return !$this->getUrl()->matches($item->getUrl());
+ }
+
+ /**
+ * Create and return the given renderer
+ *
+ * @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;
+ $classPath = null;
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ $classPath = 'Icinga\\Module\\' . ucfirst($module->getName()) . '\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath($options);
+ break;
+ }
+ }
+
+ if ($renderer === null) {
+ $classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath($options);
+ }
+ }
+
+ 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|array|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) || is_array($renderer)) {
+ $renderer = $this->createRenderer($renderer);
+ } elseif (! $renderer instanceof NavigationItemRenderer) {
+ throw new InvalidArgumentException(
+ 'Argument $renderer must be of type string, array 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;
+ }
+
+ /**
+ * 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()
+ {
+ if ($this->render === null) {
+ return $this->getUrl() !== null;
+ }
+
+ 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
+ *
+ * @return string
+ */
+ public function render()
+ {
+ try {
+ return $this->getRenderer()->setItem($this)->render();
+ } catch (Exception $e) {
+ Logger::error(
+ 'Could not invoke custom navigation item renderer. %s in %s:%d with message: %s',
+ get_class($e),
+ $e->getFile(),
+ $e->getLine(),
+ $e->getMessage()
+ );
+
+ $renderer = new NavigationItemRenderer();
+ return $renderer->render($this);
+ }
+ }
+
+ /**
+ * Return this item rendered to HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return IcingaException::describe($e);
+ }
+ }
+
+ /**
+ * Resolve all macros in the given URL
+ *
+ * @param string $url
+ *
+ * @return string
+ */
+ protected function resolveMacros($url)
+ {
+ if (strpos($url, '$') === false) {
+ return $url;
+ }
+
+ $macros = [];
+ if (Auth::getInstance()->isAuthenticated()) {
+ $macros['$user.local_name$'] = Auth::getInstance()->getUser()->getLocalUsername();
+ }
+ if (! empty($macros)) {
+ $url = str_replace(array_keys($macros), array_values($macros), $url);
+ }
+
+ return $url;
+ }
+}