summaryrefslogtreecommitdiffstats
path: root/library/Director/Web/Form/Element
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Director/Web/Form/Element/Boolean.php90
-rw-r--r--library/Director/Web/Form/Element/DataFilter.php361
-rw-r--r--library/Director/Web/Form/Element/ExtensibleSet.php89
-rw-r--r--library/Director/Web/Form/Element/FormElement.php9
-rw-r--r--library/Director/Web/Form/Element/InstanceSummary.php51
-rw-r--r--library/Director/Web/Form/Element/OptionalYesNo.php22
-rw-r--r--library/Director/Web/Form/Element/SimpleNote.php34
-rw-r--r--library/Director/Web/Form/Element/StoredPassword.php62
-rw-r--r--library/Director/Web/Form/Element/Text.php16
-rw-r--r--library/Director/Web/Form/Element/YesNo.php14
10 files changed, 748 insertions, 0 deletions
diff --git a/library/Director/Web/Form/Element/Boolean.php b/library/Director/Web/Form/Element/Boolean.php
new file mode 100644
index 0000000..b2402c7
--- /dev/null
+++ b/library/Director/Web/Form/Element/Boolean.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Zend_Form_Element_Select as ZfSelect;
+
+/**
+ * Input control for booleans
+ */
+class Boolean extends ZfSelect
+{
+ public $options = array(
+ null => '- please choose -',
+ 'y' => 'Yes',
+ 'n' => 'No',
+ );
+
+ public function getValue()
+ {
+ $value = $this->getUnfilteredValue();
+
+ if ($value === 'y' || $value === true) {
+ return true;
+ } elseif ($value === 'n' || $value === false) {
+ return false;
+ }
+
+ return null;
+ }
+
+ public function isValid($value, $context = null)
+ {
+ if ($value === 'y' || $value === 'n') {
+ $this->setValue($value);
+ return true;
+ }
+
+ return parent::isValid($value, $context);
+ }
+
+ /**
+ * @param string $value
+ * @param string $key
+ * @codingStandardsIgnoreStart
+ */
+ protected function _filterValue(&$value, &$key)
+ {
+ // @codingStandardsIgnoreEnd
+ if ($value === true) {
+ $value = 'y';
+ } elseif ($value === false) {
+ $value = 'n';
+ } elseif ($value === '') {
+ $value = null;
+ }
+
+ parent::_filterValue($value, $key);
+ }
+
+ public function setValue($value)
+ {
+ if ($value === true) {
+ $value = 'y';
+ } elseif ($value === false) {
+ $value = 'n';
+ } elseif ($value === '') {
+ $value = null;
+ }
+
+ return parent::setValue($value);
+ }
+
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ protected function _translateOption($option, $value)
+ {
+ // @codingStandardsIgnoreEnd
+ if (!isset($this->_translated[$option]) && !empty($value)) {
+ $this->options[$option] = mt('director', $value);
+ if ($this->options[$option] === $value) {
+ return false;
+ }
+ $this->_translated[$option] = true;
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/Director/Web/Form/Element/DataFilter.php b/library/Director/Web/Form/Element/DataFilter.php
new file mode 100644
index 0000000..adae07d
--- /dev/null
+++ b/library/Director/Web/Form/Element/DataFilter.php
@@ -0,0 +1,361 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Director\Web\Form\IconHelper;
+use Exception;
+
+/**
+ * Input control for extensible sets
+ */
+class DataFilter extends FormElement
+{
+ /**
+ * Default form view helper to use for rendering
+ * @var string
+ */
+ public $helper = 'formDataFilter';
+
+ private $addTo;
+
+ private $removeFilter;
+
+ private $stripFilter;
+
+ /** @var FilterChain */
+ private $filter;
+
+ public function getValue()
+ {
+ $value = parent::getValue();
+ if ($value !== null && $this->isEmpty($value)) {
+ $value = null;
+ }
+
+ return $value;
+ }
+
+ protected function isEmpty(Filter $filter)
+ {
+ return $filter->isEmpty() || $this->isEmptyExpression($filter);
+ }
+
+ protected function isEmptyExpression(Filter $filter)
+ {
+ return $filter instanceof FilterExpression &&
+ $filter->getColumn() === '' &&
+ $filter->getExpression() === '""'; // -> json_encode('')
+ }
+
+ /**
+ * @inheritdoc
+ * @codingStandardsIgnoreStart
+ */
+ protected function _filterValue(&$value, &$key)
+ {
+ // @codingStandardsIgnoreEnd
+ try {
+ if ($value instanceof Filter) {
+ // OK
+ } elseif (is_string($value)) {
+ $value = Filter::fromQueryString($value);
+ } elseif (is_array($value) || is_null($value)) {
+ $value = $this->arrayToFilter($value);
+ } else {
+ throw new ProgrammingError(
+ 'Value to be filtered has to be Filter, string, array or null'
+ );
+ }
+ } catch (Exception $e) {
+ $value = null;
+ // TODO: getFile, getLine
+ // Hint: cannot addMessage at it would loop through getValue
+ $this->addErrorMessage($e->getMessage());
+ $this->_isErrorForced = true;
+ }
+ }
+
+ /**
+ * This method transforms filter form data into a filter
+ * and reacts on pressed buttons
+ *
+ * @param array|null $array
+ *
+ * @return FilterChain|null
+ */
+ protected function arrayToFilter($array)
+ {
+ if ($array === null) {
+ return null;
+ }
+
+ $this->filter = null;
+ foreach ($array as $id => $entry) {
+ $filterId = $this->idToFilterId($id);
+ $sub = $this->entryToFilter($entry);
+ $this->checkEntryForActions($filterId, $entry);
+ $parentId = $this->parentIdFor($filterId);
+
+ if ($this->filter === null) {
+ $this->filter = $sub;
+ } else {
+ $this->filter->getById($parentId)->addFilter($sub);
+ }
+ }
+
+ $this->removeFilterIfRequested()
+ ->stripFilterIfRequested()
+ ->addNewFilterIfRequested()
+ ->fixNotsWithMultipleChildren();
+
+ return $this->filter;
+ }
+
+ protected function removeFilterIfRequested()
+ {
+ if ($this->removeFilter !== null) {
+ if ($this->filter->getById($this->removeFilter)->isRootNode()) {
+ $this->filter = $this->emptyExpression();
+ } else {
+ $this->filter->removeId($this->removeFilter);
+ }
+ }
+
+ return $this;
+ }
+
+
+ protected function stripFilterIfRequested()
+ {
+ if ($this->stripFilter !== null) {
+ $strip = $this->stripFilter;
+ $subId = $strip . '-1';
+ if ($this->filter->getId() === $strip) {
+ $this->filter = $this->filter->getById($subId);
+ } else {
+ $this->filter->replaceById($strip, $this->filter->getById($subId));
+ }
+ }
+
+ return $this;
+ }
+
+ protected function addNewFilterIfRequested()
+ {
+ if ($this->addTo !== null) {
+ $parent = $this->filter->getById($this->addTo);
+
+ if ($parent instanceof FilterChain) {
+ if ($parent->isEmpty()) {
+ $parent->addFilter($this->emptyExpression());
+ } else {
+ $parent->addFilter($this->emptyExpression());
+ }
+ } elseif ($parent instanceof FilterExpression) {
+ $replacement = Filter::matchAll(clone($parent));
+ if ($parent->isRootNode()) {
+ $this->filter = $replacement;
+ } else {
+ $this->filter->replaceById($parent->getId(), $replacement);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ protected function fixNotsWithMultipleChildren()
+ {
+ $this->filter = $this->fixNotsWithMultipleChildrenForFilter($this->filter);
+ return $this;
+ }
+
+ protected function fixNotsWithMultipleChildrenForFilter(Filter $filter)
+ {
+ if ($filter instanceof FilterChain) {
+ if ($filter->getOperatorName() === 'NOT') {
+ if ($filter->count() > 1) {
+ $filter = $this->notToNotAnd($filter);
+ }
+ }
+ /** @var Filter $sub */
+ foreach ($filter->filters() as $sub) {
+ $filter->replaceById(
+ $sub->getId(),
+ $this->fixNotsWithMultipleChildrenForFilter($sub)
+ );
+ }
+ }
+
+ return $filter;
+ }
+
+ protected function notToNotAnd(FilterChain $not)
+ {
+ $and = Filter::matchAll();
+ foreach ($not->filters() as $sub) {
+ $and->addFilter(clone($sub));
+ }
+
+ return Filter::not($and);
+ }
+
+ protected function emptyExpression()
+ {
+ return Filter::expression('', '=', '');
+ }
+
+ protected function parentIdFor($id)
+ {
+ if (false === ($pos = strrpos($id, '-'))) {
+ return '0';
+ } else {
+ return substr($id, 0, $pos);
+ }
+ }
+
+ protected function idToFilterId($id)
+ {
+ if (! preg_match('/^id_(new_)?(\d+(?:-\d+)*)$/', $id, $m)) {
+ die('nono' . $id);
+ }
+
+ return $m[2];
+ }
+
+ protected function checkEntryForActions($filterId, $entry)
+ {
+ switch ($this->entryAction($entry)) {
+ case 'cancel':
+ $this->removeFilter = $filterId;
+ break;
+
+ case 'minus':
+ $this->stripFilter = $filterId;
+ break;
+
+ case 'plus':
+ case 'angle-double-right':
+ $this->addTo = $filterId;
+ break;
+ }
+ }
+
+ /**
+ * Transforms a single submitted form component from an array
+ * into a Filter object
+ *
+ * @param array $entry The array as submitted through the form
+ *
+ * @return Filter
+ */
+ protected function entryToFilter($entry)
+ {
+ if (array_key_exists('operator', $entry)) {
+ return Filter::chain($entry['operator']);
+ } else {
+ return $this->entryToFilterExpression($entry);
+ }
+ }
+
+ protected function entryToFilterExpression($entry)
+ {
+ if ($entry['sign'] === 'true') {
+ return Filter::expression(
+ $entry['column'],
+ '=',
+ json_encode(true)
+ );
+ } elseif ($entry['sign'] === 'false') {
+ return Filter::expression(
+ $entry['column'],
+ '=',
+ json_encode(false)
+ );
+ } elseif ($entry['sign'] === 'in') {
+ if (array_key_exists('value', $entry)) {
+ if (is_array($entry['value'])) {
+ $value = array_filter($entry['value'], 'strlen');
+ } elseif (empty($entry['value'])) {
+ $value = array();
+ } else {
+ $value = array($entry['value']);
+ }
+ } else {
+ $value = array();
+ }
+ return Filter::expression(
+ $entry['column'],
+ '=',
+ json_encode($value)
+ );
+ } elseif ($entry['sign'] === 'contains') {
+ $value = array_key_exists('value', $entry) ? $entry['value'] : null;
+
+ return Filter::expression(
+ json_encode($value),
+ '=',
+ $entry['column']
+ );
+ } else {
+ $value = array_key_exists('value', $entry) ? $entry['value'] : null;
+
+ return Filter::expression(
+ $entry['column'],
+ $entry['sign'],
+ json_encode($value)
+ );
+ }
+ }
+
+ protected function entryAction($entry)
+ {
+ if (array_key_exists('action', $entry)) {
+ return IconHelper::instance()->characterIconName($entry['action']);
+ }
+
+ return null;
+ }
+
+ protected function hasIncompleteExpressions(Filter $filter)
+ {
+ if ($filter instanceof FilterChain) {
+ foreach ($filter->filters() as $sub) {
+ if ($this->hasIncompleteExpressions($sub)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ /** @var FilterExpression $filter */
+ if ($filter->isRootNode() && $this->isEmptyExpression($filter)) {
+ return false;
+ }
+
+ return $filter->getColumn() === '';
+ }
+ }
+
+ public function isValid($value, $context = null)
+ {
+ if (! $value instanceof Filter) {
+ // TODO: try, return false on E
+ $filter = $this->arrayToFilter($value);
+ $this->setValue($filter);
+ } else {
+ $filter = $value;
+ }
+
+ if ($this->hasIncompleteExpressions($filter)) {
+ $this->addError('The configured filter is incomplete');
+ return false;
+ }
+
+ return parent::isValid($value);
+ }
+}
diff --git a/library/Director/Web/Form/Element/ExtensibleSet.php b/library/Director/Web/Form/Element/ExtensibleSet.php
new file mode 100644
index 0000000..f3c968f
--- /dev/null
+++ b/library/Director/Web/Form/Element/ExtensibleSet.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use InvalidArgumentException;
+
+/**
+ * Input control for extensible sets
+ */
+class ExtensibleSet extends FormElement
+{
+ /**
+ * Default form view helper to use for rendering
+ * @var string
+ */
+ public $helper = 'formIplExtensibleSet';
+
+ // private $multiOptions;
+
+ public function getValue()
+ {
+ $value = parent::getValue();
+ if (is_string($value) || is_numeric($value)) {
+ $value = [$value];
+ } elseif ($value === null) {
+ return $value;
+ }
+ if (! is_array($value)) {
+ throw new InvalidArgumentException(sprintf(
+ 'ExtensibleSet expects to work with Arrays, got %s',
+ var_export($value, 1)
+ ));
+ }
+ $value = array_filter($value, 'strlen');
+
+ if (empty($value)) {
+ return null;
+ }
+
+ return $value;
+ }
+
+ /**
+ * We do not want one message per entry
+ *
+ * @codingStandardsIgnoreStart
+ */
+ protected function _getErrorMessages()
+ {
+ return $this->_errorMessages;
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ protected function _filterValue(&$value, &$key)
+ {
+ // @codingStandardsIgnoreEnd
+ if (is_array($value)) {
+ $value = array_filter($value, 'strlen');
+ } elseif (is_string($value) && !strlen($value)) {
+ $value = null;
+ }
+
+ parent::_filterValue($value, $key);
+ }
+
+ public function isValid($value, $context = null)
+ {
+ if ($value === null) {
+ $value = [];
+ }
+
+ $value = array_filter($value, 'strlen');
+ $this->setValue($value);
+ if ($this->isRequired() && empty($value)) {
+ // TODO: translate
+ $this->addError('You are required to choose at least one element');
+ return false;
+ }
+
+ if ($this->hasErrors()) {
+ return false;
+ }
+
+ return parent::isValid($value, $context);
+ }
+}
diff --git a/library/Director/Web/Form/Element/FormElement.php b/library/Director/Web/Form/Element/FormElement.php
new file mode 100644
index 0000000..c327859
--- /dev/null
+++ b/library/Director/Web/Form/Element/FormElement.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Zend_Form_Element_Xhtml;
+
+class FormElement extends Zend_Form_Element_Xhtml
+{
+}
diff --git a/library/Director/Web/Form/Element/InstanceSummary.php b/library/Director/Web/Form/Element/InstanceSummary.php
new file mode 100644
index 0000000..722ad26
--- /dev/null
+++ b/library/Director/Web/Form/Element/InstanceSummary.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use gipfl\IcingaWeb2\Link;
+use ipl\Html\Html;
+
+/**
+ * Used by the
+ */
+class InstanceSummary extends FormElement
+{
+ public $helper = 'formSimpleNote';
+
+ /**
+ * Always ignore this element
+ * @codingStandardsIgnoreStart
+ *
+ * @var boolean
+ */
+ protected $_ignore = true;
+ // @codingStandardsIgnoreEnd
+
+ private $instances;
+
+ /** @var array will be set via options */
+ protected $linkParams;
+
+ public function setValue($value)
+ {
+ $this->instances = $value;
+ return $this;
+ }
+
+ public function getValue()
+ {
+ return Html::tag('span', [
+ Html::tag('italic', 'empty'),
+ ' ',
+ Link::create('Manage Instances', 'director/data/dictionary', $this->linkParams, [
+ 'data-base-target' => '_next',
+ 'class' => 'icon-forward'
+ ])
+ ]);
+ }
+
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+}
diff --git a/library/Director/Web/Form/Element/OptionalYesNo.php b/library/Director/Web/Form/Element/OptionalYesNo.php
new file mode 100644
index 0000000..7ef6d7f
--- /dev/null
+++ b/library/Director/Web/Form/Element/OptionalYesNo.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+/**
+ * Input control for booleans, gives y/n
+ */
+class OptionalYesNo extends Boolean
+{
+ public function getValue()
+ {
+ $value = $this->getUnfilteredValue();
+
+ if ($value === 'y' || $value === true) {
+ return 'y';
+ } elseif ($value === 'n' || $value === false) {
+ return 'n';
+ }
+
+ return null;
+ }
+}
diff --git a/library/Director/Web/Form/Element/SimpleNote.php b/library/Director/Web/Form/Element/SimpleNote.php
new file mode 100644
index 0000000..3097e11
--- /dev/null
+++ b/library/Director/Web/Form/Element/SimpleNote.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Icinga\Module\Director\PlainObjectRenderer;
+use ipl\Html\ValidHtml;
+
+class SimpleNote extends FormElement
+{
+ public $helper = 'formSimpleNote';
+
+ /**
+ * Always ignore this element
+ * @codingStandardsIgnoreStart
+ *
+ * @var boolean
+ */
+ protected $_ignore = true;
+ // @codingStandardsIgnoreEnd
+
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+
+ public function setValue($value)
+ {
+ if (is_object($value) && ! $value instanceof ValidHtml) {
+ $value = 'Unexpected object: ' . PlainObjectRenderer::render($value);
+ }
+
+ return parent::setValue($value);
+ }
+}
diff --git a/library/Director/Web/Form/Element/StoredPassword.php b/library/Director/Web/Form/Element/StoredPassword.php
new file mode 100644
index 0000000..fa0545b
--- /dev/null
+++ b/library/Director/Web/Form/Element/StoredPassword.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Zend_Form_Element_Text as ZfText;
+
+/**
+ * StoredPassword
+ *
+ * This is a special form field and it might look a little bit weird at first
+ * sight. It's main use-case are stored cleartext passwords a user should be
+ * allowed to change.
+ *
+ * While this might sound simple, it's quite tricky if you try to fulfill the
+ * following requirements:
+ *
+ * - the current password should not be rendered to the HTML page (unless the
+ * user decides to change it)
+ * - it must be possible to visually distinct whether a password has been set
+ * - it should be impossible to "see" the length of the stored password
+ * - a changed password must be persisted
+ * - forms might be subject to multiple submissions in case other fields fail.
+ * If the user changed the password during the first submission attempt, the
+ * new string should not be lost.
+ * - all this must happen within the bounds of ZF1 form elements and related
+ * view helpers. This means that there is no related context available - and
+ * we do not know whether the form has been submitted and whether the current
+ * values have been populated from DB
+ *
+ * @package Icinga\Module\Director\Web\Form\Element
+ */
+class StoredPassword extends ZfText
+{
+ const UNCHANGED = '__UNCHANGED_VALUE__';
+
+ public $helper = 'formStoredPassword';
+
+ public function setValue($value)
+ {
+ if (\is_array($value) && isset($value['_value'], $value['_sent'])
+ && $value['_sent'] === 'y'
+ ) {
+ $value = $sentValue = $value['_value'];
+ if ($sentValue !== self::UNCHANGED) {
+ $this->setAttrib('sentValue', $sentValue);
+ }
+ } else {
+ $sentValue = null;
+ }
+
+ if ($value === self::UNCHANGED) {
+ return $this;
+ } else {
+ // Workaround for issue with modified DataTypes. This is Director-specific
+ if (\is_array($value)) {
+ $value = \json_encode($value);
+ }
+
+ return parent::setValue((string) $value);
+ }
+ }
+}
diff --git a/library/Director/Web/Form/Element/Text.php b/library/Director/Web/Form/Element/Text.php
new file mode 100644
index 0000000..eeb36f1
--- /dev/null
+++ b/library/Director/Web/Form/Element/Text.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+use Zend_Form_Element_Text as ZfText;
+
+class Text extends ZfText
+{
+ public function setValue($value)
+ {
+ if (\is_array($value)) {
+ $value = \json_encode($value);
+ }
+ return parent::setValue((string) $value);
+ }
+}
diff --git a/library/Director/Web/Form/Element/YesNo.php b/library/Director/Web/Form/Element/YesNo.php
new file mode 100644
index 0000000..3e8aaa7
--- /dev/null
+++ b/library/Director/Web/Form/Element/YesNo.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Form\Element;
+
+/**
+ * Input control for booleans, gives y/n
+ */
+class YesNo extends OptionalYesNo
+{
+ public $options = array(
+ 'y' => 'Yes',
+ 'n' => 'No',
+ );
+}