summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Web/Form/Decorator
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Icinga/Web/Form/Decorator/Autosubmit.php133
-rw-r--r--library/Icinga/Web/Form/Decorator/ConditionalHidden.php35
-rw-r--r--library/Icinga/Web/Form/Decorator/ElementDoubler.php63
-rw-r--r--library/Icinga/Web/Form/Decorator/FormDescriptions.php76
-rw-r--r--library/Icinga/Web/Form/Decorator/FormHints.php142
-rw-r--r--library/Icinga/Web/Form/Decorator/FormNotifications.php125
-rw-r--r--library/Icinga/Web/Form/Decorator/Help.php113
-rw-r--r--library/Icinga/Web/Form/Decorator/Spinner.php48
8 files changed, 735 insertions, 0 deletions
diff --git a/library/Icinga/Web/Form/Decorator/Autosubmit.php b/library/Icinga/Web/Form/Decorator/Autosubmit.php
new file mode 100644
index 0000000..4405d0b
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Autosubmit.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+use Icinga\Web\Form;
+
+/**
+ * Decorator to add an icon and a submit button encapsulated in noscript-tags
+ *
+ * The icon is shown in JS environments to indicate that a specific form field does automatically request an update
+ * of its form upon it has changed. The button allows users in non-JS environments to trigger the update manually.
+ */
+class Autosubmit extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Whether a hidden <span> should be created with the same warning as in the icon label
+ *
+ * @var bool
+ */
+ protected $accessible;
+
+ /**
+ * The id used to identify the auto-submit warning associated with the decorated form element
+ *
+ * @var string
+ */
+ protected $warningId;
+
+ /**
+ * Set whether a hidden <span> should be created with the same warning as in the icon label
+ *
+ * @param bool $state
+ *
+ * @return Autosubmit
+ */
+ public function setAccessible($state = true)
+ {
+ $this->accessible = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether a hidden <span> is being created with the same warning as in the icon label
+ *
+ * @return bool
+ */
+ public function getAccessible()
+ {
+ if ($this->accessible === null) {
+ $this->accessible = $this->getOption('accessible') ?: false;
+ }
+
+ return $this->accessible;
+ }
+
+ /**
+ * Return the id used to identify the auto-submit warning associated with the decorated element
+ *
+ * @param mixed $element The element for which to generate a id
+ *
+ * @return string
+ */
+ public function getWarningId($element = null)
+ {
+ if ($this->warningId === null) {
+ $element = $element ?: $this->getElement();
+ $this->warningId = 'autosubmit_warning_' . $element->getId();
+ }
+
+ return $this->warningId;
+ }
+
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a auto-submit icon and submit button encapsulated in noscript-tags to the element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ if ($content) {
+ $isForm = $this->getElement() instanceof Form;
+ $warning = $isForm
+ ? t('This page will be automatically updated upon change of any of this form\'s fields')
+ : t('This page will be automatically updated upon change of the value');
+ $content .= $this->getView()->icon('cw', $warning, array(
+ 'aria-hidden' => $isForm ? 'false' : 'true',
+ 'class' => 'spinner autosubmit-info'
+ ));
+ if (! $isForm && $this->getAccessible()) {
+ $content = '<span id="'
+ . $this->getWarningId()
+ . '" class="sr-only">'
+ . $warning
+ . '</span>'
+ . $content;
+ }
+
+ $content .= sprintf(
+ '<noscript><button'
+ . ' name="noscript_apply"'
+ . ' class="noscript-apply"'
+ . ' type="submit"'
+ . ' value="1"'
+ . ($this->getAccessible() ? ' aria-label="%1$s"' : '')
+ . ' title="%1$s"'
+ . '>%2$s</button></noscript>',
+ $isForm
+ ? t('Push this button to update the form to reflect the changes that were made below')
+ : t('Push this button to update the form to reflect the change'
+ . ' that was made in the field on the left'),
+ $this->getView()->icon('cw') . t('Apply')
+ );
+ }
+
+ return $content;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/ConditionalHidden.php b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php
new file mode 100644
index 0000000..0f84535
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to hide elements using a &gt;noscript&lt; tag instead of
+ * type='hidden' or css styles.
+ *
+ * This allows to hide depending elements for browsers with javascript
+ * (who can then automatically refresh their pages) but show them in
+ * case JavaScript is disabled
+ */
+class ConditionalHidden extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Generate a field that will be wrapped in <noscript> tag if the
+ * "condition" attribute is set and false or 0
+ *
+ * @param string $content The tag's content
+ *
+ * @return string The generated tag
+ */
+ public function render($content = '')
+ {
+ $attributes = $this->getElement()->getAttribs();
+ $condition = isset($attributes['condition']) ? $attributes['condition'] : 1;
+ if ($condition != 1) {
+ $content = '<noscript>' . $content . '</noscript>';
+ }
+ return $content;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/ElementDoubler.php b/library/Icinga/Web/Form/Decorator/ElementDoubler.php
new file mode 100644
index 0000000..2da5646
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/ElementDoubler.php
@@ -0,0 +1,63 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Element;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * A decorator that will double a single element of a display group
+ *
+ * The options `condition', `double' and `attributes' can be passed to the constructor and are used to affect whether
+ * the doubling should take effect, which element should be doubled and which HTML attributes should be applied to the
+ * doubled element, respectively.
+ *
+ * `condition' must be an element's name that when it's part of the display group causes the condition to be met.
+ * `double' must be an element's name and must be part of the display group.
+ * `attributes' is just an array of key-value pairs.
+ *
+ * You can also pass `placement' to control whether the doubled element is prepended or appended.
+ */
+class ElementDoubler extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Return the display group's elements with an additional copy of an element being added if the condition is met
+ *
+ * @param string $content The HTML rendered so far
+ *
+ * @return string
+ */
+ public function render($content)
+ {
+ $group = $this->getElement();
+ if ($group->getElement($this->getOption('condition')) !== null) {
+ if ($this->getPlacement() === static::APPEND) {
+ return $content . $this->applyAttributes($group->getElement($this->getOption('double')))->render();
+ } else { // $this->getPlacement() === static::PREPEND
+ return $this->applyAttributes($group->getElement($this->getOption('double')))->render() . $content;
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Apply all element attributes
+ *
+ * @param Zend_Form_Element $element The element to apply the attributes to
+ *
+ * @return Zend_Form_Element
+ */
+ protected function applyAttributes(Zend_Form_Element $element)
+ {
+ $attributes = $this->getOption('attributes');
+ if ($attributes !== null) {
+ foreach ($attributes as $name => $value) {
+ $element->setAttrib($name, $value);
+ }
+ }
+
+ return $element;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormDescriptions.php b/library/Icinga/Web/Form/Decorator/FormDescriptions.php
new file mode 100644
index 0000000..88ea5d9
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormDescriptions.php
@@ -0,0 +1,76 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Form;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to add a list of descriptions at the top or bottom of a form
+ */
+class FormDescriptions extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render form descriptions
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $descriptions = $this->recurseForm($form);
+ if (empty($descriptions)) {
+ return $content;
+ }
+
+ $html = '<div class="form-description">'
+ . Icinga::app()->getViewRenderer()->view->icon('info-circled', '', ['class' => 'form-description-icon'])
+ . '<ul class="form-description-list">';
+
+ foreach ($descriptions as $description) {
+ if (is_array($description)) {
+ list($description, $properties) = $description;
+ $html .= '<li' . $view->propertiesToString($properties) . '>' . $view->escape($description) . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($description) . '</li>';
+ }
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul></div>';
+ case self::PREPEND:
+ return $html . '</ul></div>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the descriptions for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form)
+ {
+ $descriptions = array($form->getDescriptions());
+ foreach ($form->getSubForms() as $subForm) {
+ $descriptions[] = $this->recurseForm($subForm);
+ }
+
+ return call_user_func_array('array_merge', $descriptions);
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormHints.php b/library/Icinga/Web/Form/Decorator/FormHints.php
new file mode 100644
index 0000000..797be26
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormHints.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Web\Form;
+
+/**
+ * Decorator to add a list of hints at the top or bottom of a form
+ *
+ * The hint for required form elements is automatically being handled.
+ */
+class FormHints extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * A list of element class names to be ignored when detecting which message to use to describe required elements
+ *
+ * @var array
+ */
+ protected $blacklist;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($options = null)
+ {
+ parent::__construct($options);
+ $this->blacklist = array(
+ 'Zend_Form_Element_Hidden',
+ 'Zend_Form_Element_Submit',
+ 'Zend_Form_Element_Button',
+ 'Icinga\Web\Form\Element\Note',
+ 'Icinga\Web\Form\Element\Button',
+ 'Icinga\Web\Form\Element\CsrfCounterMeasure'
+ );
+ }
+
+ /**
+ * Render form hints
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $hints = $this->recurseForm($form, $entirelyRequired);
+ if ($entirelyRequired !== null) {
+ $hints[] = sprintf(
+ $form->getView()->translate('%s Required field'),
+ $form->getRequiredCue()
+ );
+ }
+
+ if (empty($hints)) {
+ return $content;
+ }
+
+ $html = '<ul class="form-info">';
+ foreach ($hints as $hint) {
+ if (is_array($hint)) {
+ list($hint, $properties) = $hint;
+ $html .= '<li' . $view->propertiesToString($properties) . '>' . $view->escape($hint) . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($hint) . '</li>';
+ }
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul>';
+ case self::PREPEND:
+ return $html . '</ul>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the hints for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ * @param mixed $entirelyRequired Set by reference, true means all elements in the hierarchy are
+ * required, false only a partial subset and null none at all
+ * @param bool $elementsPassed Whether there were any elements passed during the recursion until now
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form, &$entirelyRequired = null, $elementsPassed = false)
+ {
+ $requiredLabels = array();
+ if ($form->getRequiredCue() !== null) {
+ $partiallyRequired = $partiallyOptional = false;
+ foreach ($form->getElements() as $element) {
+ if (! in_array($element->getType(), $this->blacklist)) {
+ if (! $element->isRequired()) {
+ $partiallyOptional = true;
+ if ($entirelyRequired) {
+ $entirelyRequired = false;
+ }
+ } else {
+ $partiallyRequired = true;
+ if (($label = $element->getDecorator('label')) !== false) {
+ $requiredLabels[] = $label;
+ }
+ }
+ }
+ }
+
+ if (! $elementsPassed) {
+ $elementsPassed = $partiallyRequired || $partiallyOptional;
+ if ($entirelyRequired === null && $partiallyRequired) {
+ $entirelyRequired = ! $partiallyOptional;
+ }
+ } elseif ($entirelyRequired === null && $partiallyRequired) {
+ $entirelyRequired = false;
+ }
+ }
+
+ $hints = array($form->getHints());
+ foreach ($form->getSubForms() as $subForm) {
+ $hints[] = $this->recurseForm($subForm, $entirelyRequired, $elementsPassed);
+ }
+
+ if ($entirelyRequired) {
+ foreach ($requiredLabels as $label) {
+ $label->setRequiredSuffix('');
+ }
+ }
+
+ return call_user_func_array('array_merge', $hints);
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormNotifications.php b/library/Icinga/Web/Form/Decorator/FormNotifications.php
new file mode 100644
index 0000000..46734df
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormNotifications.php
@@ -0,0 +1,125 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Form;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to add a list of notifications at the top or bottom of a form
+ */
+class FormNotifications extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render form notifications
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $notifications = $this->recurseForm($form);
+ if (empty($notifications)) {
+ return $content;
+ }
+
+ $html = '<ul class="form-notification-list">';
+ foreach (array(Form::NOTIFICATION_ERROR, Form::NOTIFICATION_WARNING, Form::NOTIFICATION_INFO) as $type) {
+ if (isset($notifications[$type])) {
+ $html .= '<li><ul class="notification-' . $this->getNotificationTypeName($type) . '">';
+ foreach ($notifications[$type] as $message) {
+ if (is_array($message)) {
+ list($message, $properties) = $message;
+ $html .= '<li' . $view->propertiesToString($properties) . '>'
+ . $view->escape($message)
+ . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($message) . '</li>';
+ }
+ }
+
+ $html .= '</ul></li>';
+ }
+ }
+
+ if (isset($notifications[Form::NOTIFICATION_ERROR])) {
+ $icon = 'cancel';
+ $class = 'error';
+ } elseif (isset($notifications[Form::NOTIFICATION_WARNING])) {
+ $icon = 'warning-empty';
+ $class = 'warning';
+ } else {
+ $icon = 'info';
+ $class = 'info';
+ }
+
+ $html = "<div class=\"form-notifications $class\">"
+ . Icinga::app()->getViewRenderer()->view->icon($icon, '', ['class' => 'form-notification-icon'])
+ . $html;
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul></div>';
+ case self::PREPEND:
+ return $html . '</ul></div>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the notifications for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form)
+ {
+ $notifications = $form->getNotifications();
+ foreach ($form->getSubForms() as $subForm) {
+ foreach ($this->recurseForm($subForm) as $type => $messages) {
+ foreach ($messages as $message) {
+ $notifications[$type][] = $message;
+ }
+ }
+ }
+
+ return $notifications;
+ }
+
+ /**
+ * Return the name for the given notification type
+ *
+ * @param int $type
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the given type is invalid
+ */
+ protected function getNotificationTypeName($type)
+ {
+ switch ($type) {
+ case Form::NOTIFICATION_ERROR:
+ return 'error';
+ case Form::NOTIFICATION_WARNING:
+ return 'warning';
+ case Form::NOTIFICATION_INFO:
+ return 'info';
+ default:
+ throw new ProgrammingError('Invalid notification type "%s" provided', $type);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/Help.php b/library/Icinga/Web/Form/Decorator/Help.php
new file mode 100644
index 0000000..c521abe
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Help.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Element;
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * Decorator to add helptext to a form element
+ */
+class Help extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Whether a hidden <span> should be created to describe the decorated form element
+ *
+ * @var bool
+ */
+ protected $accessible = false;
+
+ /**
+ * The id used to identify the description associated with the decorated form element
+ *
+ * @var string
+ */
+ protected $descriptionId;
+
+ /**
+ * Set whether a hidden <span> should be created to describe the decorated form element
+ *
+ * @param bool $state
+ *
+ * @return Help
+ */
+ public function setAccessible($state = true)
+ {
+ $this->accessible = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the id used to identify the description associated with the decorated element
+ *
+ * @param Zend_Form_Element $element The element for which to generate a id
+ *
+ * @return string
+ */
+ public function getDescriptionId(Zend_Form_Element $element = null)
+ {
+ if ($this->descriptionId === null) {
+ $element = $element ?: $this->getElement();
+ $this->descriptionId = 'desc_' . $element->getId();
+ }
+
+ return $this->descriptionId;
+ }
+
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a help icon to the left of an element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $element = $this->getElement();
+ $description = $element->getDescription();
+ $requirement = $element->getAttrib('requirement');
+ unset($element->requirement);
+
+ $helpContent = '';
+ if ($description || $requirement) {
+ if ($this->accessible) {
+ $helpContent = '<span id="'
+ . $this->getDescriptionId()
+ . '" class="sr-only">'
+ . $description
+ . ($description && $requirement ? ' ' : '')
+ . $requirement
+ . '</span>';
+ }
+
+ $helpContent = $this->getView()->icon(
+ 'info-circled',
+ $description . ($description && $requirement ? ' ' : '') . $requirement,
+ array(
+ 'class' => 'control-info',
+ 'aria-hidden' => $this->accessible ? 'true' : 'false'
+ )
+ ) . $helpContent;
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $helpContent;
+ case self::PREPEND:
+ return $helpContent . $content;
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/Spinner.php b/library/Icinga/Web/Form/Decorator/Spinner.php
new file mode 100644
index 0000000..9cfa568
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Spinner.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * Decorator to add a spinner next to an element
+ */
+class Spinner extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a spinner icon to a form element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $spinner = '<div '
+ . ($this->getOption('id') !== null ? ' id="' . $this->getOption('id') . '"' : '')
+ . 'class="spinner ' . ($this->getOption('class') ?: '') . '"'
+ . '>'
+ . $this->getView()->icon('spin6')
+ . '</div>';
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $spinner;
+ case self::PREPEND:
+ return $spinner . $content;
+ }
+ }
+}