diff options
Diffstat (limited to '')
-rw-r--r-- | library/Director/Web/Form/Element/Boolean.php | 90 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/DataFilter.php | 361 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/ExtensibleSet.php | 89 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/FormElement.php | 9 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/InstanceSummary.php | 51 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/OptionalYesNo.php | 22 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/SimpleNote.php | 34 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/StoredPassword.php | 62 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/Text.php | 16 | ||||
-rw-r--r-- | library/Director/Web/Form/Element/YesNo.php | 14 |
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', + ); +} |