diff options
Diffstat (limited to 'library/Businessprocess/Web/Form')
10 files changed, 982 insertions, 0 deletions
diff --git a/library/Businessprocess/Web/Form/BpConfigBaseForm.php b/library/Businessprocess/Web/Form/BpConfigBaseForm.php new file mode 100644 index 0000000..ddfc851 --- /dev/null +++ b/library/Businessprocess/Web/Form/BpConfigBaseForm.php @@ -0,0 +1,72 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form; + +use Icinga\Application\Config; +use Icinga\Application\Icinga; +use Icinga\Authentication\Auth; +use Icinga\Module\Businessprocess\Storage\LegacyStorage; +use Icinga\Module\Businessprocess\BpConfig; + +abstract class BpConfigBaseForm extends QuickForm +{ + /** @var LegacyStorage */ + protected $storage; + + /** @var BpConfig */ + protected $config; + + protected function listAvailableBackends() + { + $keys = []; + $moduleManager = Icinga::app()->getModuleManager(); + if ($moduleManager->hasEnabled('monitoring')) { + $keys = array_keys(Config::module('monitoring', 'backends')->toArray()); + $keys = array_combine($keys, $keys); + } + + return $keys; + } + + public function setStorage(LegacyStorage $storage) + { + $this->storage = $storage; + return $this; + } + + public function setProcessConfig(BpConfig $config) + { + $this->config = $config; + return $this; + } + + protected function prepareMetadata(BpConfig $config) + { + $meta = $config->getMetadata(); + $auth = Auth::getInstance(); + $meta->set('Owner', $auth->getUser()->getUsername()); + + if ($auth->hasPermission('businessprocess/showall')) { + return true; + } + + $prefixes = $auth->getRestrictions('businessprocess/prefix'); + if (! empty($prefixes) && ! $meta->nameIsPrefixedWithOneOf($prefixes)) { + if (count($prefixes) === 1) { + $this->getElement('name')->addError(sprintf( + $this->translate('Please prefix the name with "%s"'), + current($prefixes) + )); + } else { + $this->getElement('name')->addError(sprintf( + $this->translate('Please prefix the name with one of "%s"'), + implode('", "', $prefixes) + )); + } + + return false; + } + + return true; + } +} diff --git a/library/Businessprocess/Web/Form/CsrfToken.php b/library/Businessprocess/Web/Form/CsrfToken.php new file mode 100644 index 0000000..9eb24ef --- /dev/null +++ b/library/Businessprocess/Web/Form/CsrfToken.php @@ -0,0 +1,53 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form; + +class CsrfToken +{ + /** + * Check whether the given token is valid + * + * @param string $token Token + * + * @return bool + */ + public static function isValid($token) + { + if (strpos($token, '|') === false) { + return false; + } + + list($seed, $token) = explode('|', $token); + + if (!is_numeric($seed)) { + return false; + } + + return $token === hash('sha256', self::getSessionId() . $seed); + } + + /** + * Create a new token + * + * @return string + */ + public static function generate() + { + $seed = mt_rand(); + $token = hash('sha256', self::getSessionId() . $seed); + + return sprintf('%s|%s', $seed, $token); + } + + /** + * Get current session id + * + * TODO: we should do this through our App or Session object + * + * @return string + */ + protected static function getSessionId() + { + return session_id(); + } +} diff --git a/library/Businessprocess/Web/Form/Element/Checkbox.php b/library/Businessprocess/Web/Form/Element/Checkbox.php new file mode 100644 index 0000000..7975b82 --- /dev/null +++ b/library/Businessprocess/Web/Form/Element/Checkbox.php @@ -0,0 +1,8 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form\Element; + +class Checkbox extends \Icinga\Web\Form\Element\Checkbox +{ + +} diff --git a/library/Businessprocess/Web/Form/Element/FormElement.php b/library/Businessprocess/Web/Form/Element/FormElement.php new file mode 100644 index 0000000..7647a5e --- /dev/null +++ b/library/Businessprocess/Web/Form/Element/FormElement.php @@ -0,0 +1,9 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form\Element; + +use Zend_Form_Element_Xhtml; + +class FormElement extends Zend_Form_Element_Xhtml +{ +} diff --git a/library/Businessprocess/Web/Form/Element/SimpleNote.php b/library/Businessprocess/Web/Form/Element/SimpleNote.php new file mode 100644 index 0000000..9f757f2 --- /dev/null +++ b/library/Businessprocess/Web/Form/Element/SimpleNote.php @@ -0,0 +1,22 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form\Element; + +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; + } +} diff --git a/library/Businessprocess/Web/Form/Element/StateOverrides.php b/library/Businessprocess/Web/Form/Element/StateOverrides.php new file mode 100644 index 0000000..c2216c0 --- /dev/null +++ b/library/Businessprocess/Web/Form/Element/StateOverrides.php @@ -0,0 +1,55 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form\Element; + +class StateOverrides extends FormElement +{ + public $helper = 'formStateOverrides'; + + /** @var array The overridable states */ + protected $states; + + /** + * Set the overridable states + * + * @param array $states + * + * @return $this + */ + public function setStates(array $states) + { + $this->states = $states; + + return $this; + } + + /** + * Get the overridable states + * + * @return array + */ + public function getStates() + { + return $this->states; + } + + public function init() + { + $this->setIsArray(true); + } + + public function setValue($value) + { + $cleanedValue = []; + + if (! empty($value)) { + foreach ($value as $from => $to) { + if ((int) $from !== (int) $to) { + $cleanedValue[$from] = $to; + } + } + } + + return parent::setValue($cleanedValue); + } +} diff --git a/library/Businessprocess/Web/Form/FormLoader.php b/library/Businessprocess/Web/Form/FormLoader.php new file mode 100644 index 0000000..965da4b --- /dev/null +++ b/library/Businessprocess/Web/Form/FormLoader.php @@ -0,0 +1,37 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form; + +use Icinga\Application\Icinga; +use Icinga\Application\Modules\Module; +use Icinga\Exception\ProgrammingError; + +class FormLoader +{ + public static function load($name, Module $module = null) + { + if ($module === null) { + $basedir = Icinga::app()->getApplicationDir('forms'); + $ns = '\\Icinga\\Web\\Forms\\'; + } else { + $basedir = $module->getFormDir(); + $ns = '\\Icinga\\Module\\' . ucfirst($module->getName()) . '\\Forms\\'; + } + if (preg_match('~^[a-z0-9/]+$~i', $name)) { + $parts = preg_split('~/~', $name); + $class = ucfirst(array_pop($parts)) . 'Form'; + $file = sprintf('%s/%s/%s.php', rtrim($basedir, '/'), implode('/', $parts), $class); + if (file_exists($file)) { + require_once($file); + $class = $ns . $class; + $options = array(); + if ($module !== null) { + $options['icingaModule'] = $module; + } + + return new $class($options); + } + } + throw new ProgrammingError(sprintf('Cannot load %s (%s), no such form', $name, $file)); + } +} diff --git a/library/Businessprocess/Web/Form/QuickBaseForm.php b/library/Businessprocess/Web/Form/QuickBaseForm.php new file mode 100644 index 0000000..3ef7b66 --- /dev/null +++ b/library/Businessprocess/Web/Form/QuickBaseForm.php @@ -0,0 +1,167 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form; + +use Icinga\Application\Icinga; +use Icinga\Application\Modules\Module; +use ipl\Html\ValidHtml; +use Zend_Form; + +abstract class QuickBaseForm extends Zend_Form implements ValidHtml +{ + /** + * The Icinga module this form belongs to. Usually only set if the + * form is initialized through the FormLoader + * + * @var Module + */ + protected $icingaModule; + + protected $icingaModuleName; + + private $hintCount = 0; + + public function __construct($options = null) + { + $this->callZfConstructor($this->handleOptions($options)) + ->initializePrefixPaths(); + } + + protected function callZfConstructor($options = null) + { + parent::__construct($options); + return $this; + } + + protected function initializePrefixPaths() + { + $this->addPrefixPathsForBusinessprocess(); + if ($this->icingaModule && $this->icingaModuleName !== 'businessprocess') { + $this->addPrefixPathsForModule($this->icingaModule); + } + } + + protected function addPrefixPathsForBusinessprocess() + { + $module = Icinga::app() + ->getModuleManager() + ->loadModule('businessprocess') + ->getModule('businessprocess'); + + $this->addPrefixPathsForModule($module); + } + + public function addPrefixPathsForModule(Module $module) + { + $basedir = sprintf( + '%s/%s/Web/Form', + $module->getLibDir(), + ucfirst($module->getName()) + ); + + $this->addPrefixPaths(array( + array( + 'prefix' => __NAMESPACE__ . '\\Element\\', + 'path' => $basedir . '/Element', + 'type' => static::ELEMENT + ) + )); + + return $this; + } + + public function addHidden($name, $value = null) + { + $this->addElement('hidden', $name); + $el = $this->getElement($name); + $el->setDecorators(array('ViewHelper')); + if ($value !== null) { + $this->setDefault($name, $value); + $el->setValue($value); + } + + return $this; + } + + // TODO: Should be an element + public function addHtmlHint($html, $options = array()) + { + return $this->addHtml('<div class="hint">' . $html . '</div>', $options); + } + + public function addHtml($html, $options = array()) + { + if (array_key_exists('name', $options)) { + $name = $options['name']; + unset($options['name']); + } else { + $name = '_HINT' . ++$this->hintCount; + } + + $this->addElement('simpleNote', $name, $options); + $this->getElement($name) + ->setValue($html) + ->setIgnore(true) + ->setDecorators(array('ViewHelper')); + + return $this; + } + + public function optionalEnum($enum, $nullLabel = null) + { + if ($nullLabel === null) { + $nullLabel = $this->translate('- please choose -'); + } + + return array(null => $nullLabel) + $enum; + } + + protected function handleOptions($options = null) + { + if ($options === null) { + return $options; + } + + if (array_key_exists('icingaModule', $options)) { + /** @var Module icingaModule */ + $this->icingaModule = $options['icingaModule']; + $this->icingaModuleName = $this->icingaModule->getName(); + unset($options['icingaModule']); + } + + return $options; + } + + public function setIcingaModule(Module $module) + { + $this->icingaModule = $module; + return $this; + } + + protected function loadForm($name, Module $module = null) + { + if ($module === null) { + $module = $this->icingaModule; + } + + return FormLoader::load($name, $module); + } + + protected function valueIsEmpty($value) + { + if (is_array($value)) { + return empty($value); + } + + return strlen($value) === 0; + } + + public function translate($string) + { + if ($this->icingaModuleName === null) { + return t($string); + } else { + return mt($this->icingaModuleName, $string); + } + } +} diff --git a/library/Businessprocess/Web/Form/QuickForm.php b/library/Businessprocess/Web/Form/QuickForm.php new file mode 100644 index 0000000..c39b34b --- /dev/null +++ b/library/Businessprocess/Web/Form/QuickForm.php @@ -0,0 +1,502 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form; + +use Icinga\Application\Icinga; +use Icinga\Exception\ProgrammingError; +use Icinga\Web\Notification; +use Icinga\Web\Request; +use Icinga\Web\Response; +use Icinga\Web\Url; +use Exception; + +/** + * QuickForm wants to be a base class for simple forms + */ +abstract class QuickForm extends QuickBaseForm +{ + const ID = '__FORM_NAME'; + + const CSRF = '__FORM_CSRF'; + + /** + * The name of this form + */ + protected $formName; + + /** + * Whether the form has been sent + */ + protected $hasBeenSent; + + /** + * Whether the form has been sent + */ + protected $hasBeenSubmitted; + + /** + * The submit caption, element - still tbd + */ + // protected $submit; + + /** + * Our request + */ + protected $request; + + /** + * @var Url + */ + protected $successUrl; + + protected $successMessage; + + protected $submitLabel; + + protected $submitButtonName; + + protected $deleteButtonName; + + protected $fakeSubmitButtonName; + + /** + * Whether form elements have already been created + */ + protected $didSetup = false; + + protected $isApiRequest = false; + + public function __construct($options = null) + { + parent::__construct($options); + + $this->setMethod('post'); + $this->getActionFromRequest() + ->createIdElement() + ->regenerateCsrfToken() + ->setPreferredDecorators(); + } + + protected function getActionFromRequest() + { + $this->setAction(Url::fromRequest()); + return $this; + } + + protected function setPreferredDecorators() + { + $this->setAttrib('class', 'autofocus icinga-controls'); + $this->setDecorators( + array( + 'Description', + array('FormErrors', array('onlyCustomFormErrors' => true)), + 'FormElements', + 'Form' + ) + ); + + return $this; + } + + protected function addSubmitButtonIfSet() + { + if (false === ($label = $this->getSubmitLabel())) { + return; + } + + if ($this->submitButtonName && $el = $this->getElement($this->submitButtonName)) { + return; + } + + $el = $this->createElement('submit', $label) + ->setLabel($label) + ->setDecorators(array('ViewHelper')); + $this->submitButtonName = $el->getName(); + $this->addElement($el); + + $fakeEl = $this->createElement('submit', '_FAKE_SUBMIT') + ->setLabel($label) + ->setDecorators(array('ViewHelper')); + $this->fakeSubmitButtonName = $fakeEl->getName(); + $this->addElement($fakeEl); + + $this->addDisplayGroup( + array($this->fakeSubmitButtonName), + 'fake_button', + array( + 'decorators' => array('FormElements'), + 'order' => 1, + ) + ); + + $grp = array( + $this->submitButtonName, + $this->deleteButtonName + ); + $this->addDisplayGroup($grp, 'buttons', array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'dl')), + 'DtDdWrapper', + ), + 'order' => 1000, + )); + } + + protected function addSimpleDisplayGroup($elements, $name, $options) + { + if (! array_key_exists('decorators', $options)) { + $options['decorators'] = array( + 'FormElements', + array('HtmlTag', array('tag' => 'dl')), + 'Fieldset', + ); + } + + return $this->addDisplayGroup($elements, $name, $options); + } + + protected function createIdElement() + { + $this->detectName(); + $this->addHidden(self::ID, $this->getName()); + $this->getElement(self::ID)->setIgnore(true); + return $this; + } + + public function getSentValue($name, $default = null) + { + $request = $this->getRequest(); + if ($request->isPost() && $this->hasBeenSent()) { + return $request->getPost($name); + } else { + return $default; + } + } + + public function getSubmitLabel() + { + if ($this->submitLabel === null) { + return $this->translate('Submit'); + } + + return $this->submitLabel; + } + + public function setSubmitLabel($label) + { + $this->submitLabel = $label; + return $this; + } + + public function setApiRequest($isApiRequest = true) + { + $this->isApiRequest = $isApiRequest; + return $this; + } + + public function isApiRequest() + { + return $this->isApiRequest; + } + + public function regenerateCsrfToken() + { + if (! $element = $this->getElement(self::CSRF)) { + $this->addHidden(self::CSRF, CsrfToken::generate()); + $element = $this->getElement(self::CSRF); + } + $element->setIgnore(true); + + return $this; + } + + public function removeCsrfToken() + { + $this->removeElement(self::CSRF); + return $this; + } + + public function setSuccessUrl($url, $params = null) + { + if (! $url instanceof Url) { + $url = Url::fromPath($url); + } + if ($params !== null) { + $url->setParams($params); + } + $this->successUrl = $url; + return $this; + } + + public function getSuccessUrl() + { + $url = $this->successUrl ?: $this->getAction(); + if (! $url instanceof Url) { + $url = Url::fromPath($url); + } + + return $url; + } + + protected function beforeSetup() + { + } + + public function setup() + { + } + + protected function onSetup() + { + } + + public function setAction($action) + { + if ($action instanceof Url) { + $action = $action->getAbsoluteUrl('&'); + } + + return parent::setAction($action); + } + + public function hasBeenSubmitted() + { + if ($this->hasBeenSubmitted === null) { + $req = $this->getRequest(); + if ($req->isPost()) { + if (! $this->hasSubmitButton()) { + return $this->hasBeenSubmitted = $this->hasBeenSent(); + } + + $this->hasBeenSubmitted = $this->pressedButton( + $this->fakeSubmitButtonName, + $this->getSubmitLabel() + ) || $this->pressedButton( + $this->submitButtonName, + $this->getSubmitLabel() + ); + } else { + $this->hasBeenSubmitted = false; + } + } + + return $this->hasBeenSubmitted; + } + + protected function hasSubmitButton() + { + return $this->submitButtonName !== null; + } + + protected function pressedButton($name, $label) + { + $req = $this->getRequest(); + if (! $req->isPost()) { + return false; + } + + $req = $this->getRequest(); + $post = $req->getPost(); + + return array_key_exists($name, $post) + && $post[$name] === $label; + } + + protected function beforeValidation($data = array()) + { + } + + public function prepareElements() + { + if (! $this->didSetup) { + $this->beforeSetup(); + $this->setup(); + $this->addSubmitButtonIfSet(); + $this->onSetup(); + $this->didSetup = true; + } + + return $this; + } + + public function handleRequest(Request $request = null) + { + if ($request === null) { + $request = $this->getRequest(); + } else { + $this->setRequest($request); + } + + $this->prepareElements(); + + if ($this->hasBeenSent()) { + $post = $request->getPost(); + if ($this->hasBeenSubmitted()) { + $this->beforeValidation($post); + if ($this->isValid($post)) { + try { + $this->onSuccess(); + } catch (Exception $e) { + $this->addException($e); + $this->onFailure(); + } + } else { + $this->onFailure(); + } + } else { + $this->setDefaults($post); + } + } else { + // Well... + } + + return $this; + } + + public function addException(Exception $e, $elementName = null) + { + $file = preg_split('/[\/\\\]/', $e->getFile(), -1, PREG_SPLIT_NO_EMPTY); + $file = array_pop($file); + $msg = sprintf( + '%s (%s:%d)', + $e->getMessage(), + $file, + $e->getLine() + ); + + if ($el = $this->getElement($elementName)) { + $el->addError($msg); + } else { + $this->addError($msg); + } + } + + public function onSuccess() + { + $this->redirectOnSuccess(); + } + + public function setSuccessMessage($message) + { + $this->successMessage = $message; + return $this; + } + + public function getSuccessMessage($message = null) + { + if ($message !== null) { + return $message; + } + if ($this->successMessage === null) { + return t('Form has successfully been sent'); + } + return $this->successMessage; + } + + public function redirectOnSuccess($message = null) + { + if ($this->isApiRequest()) { + // TODO: Set the status line message? + $this->successMessage = $this->getSuccessMessage($message); + return; + } + + $url = $this->getSuccessUrl(); + $this->notifySuccess($this->getSuccessMessage($message)); + $this->redirectAndExit($url); + } + + public function onFailure() + { + } + + public function notifySuccess($message = null) + { + if ($message === null) { + $message = t('Form has successfully been sent'); + } + Notification::success($message); + return $this; + } + + public function notifyError($message) + { + Notification::error($message); + return $this; + } + + protected function redirectAndExit($url) + { + /** @var Response $response */ + $response = Icinga::app()->getFrontController()->getResponse(); + $response->redirectAndExit($url); + } + + protected function setHttpResponseCode($code) + { + Icinga::app()->getFrontController()->getResponse()->setHttpResponseCode($code); + return $this; + } + + protected function onRequest() + { + } + + public function setRequest(Request $request) + { + if ($this->request !== null) { + throw new ProgrammingError('Unable to set request twice'); + } + + $this->request = $request; + $this->prepareElements(); + $this->onRequest(); + return $this; + } + + /** + * @return Request + */ + public function getRequest() + { + if ($this->request === null) { + /** @var Request $request */ + $request = Icinga::app()->getFrontController()->getRequest(); + $this->setRequest($request); + } + return $this->request; + } + + public function hasBeenSent() + { + if ($this->hasBeenSent === null) { + + /** @var Request $req */ + if ($this->request === null) { + $req = Icinga::app()->getFrontController()->getRequest(); + } else { + $req = $this->request; + } + + if ($req->isPost()) { + $post = $req->getPost(); + $this->hasBeenSent = array_key_exists(self::ID, $post) && + $post[self::ID] === $this->getName(); + } else { + $this->hasBeenSent = false; + } + } + + return $this->hasBeenSent; + } + + protected function detectName() + { + if ($this->formName !== null) { + $this->setName($this->formName); + } else { + $this->setName(get_class($this)); + } + } +} diff --git a/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php b/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php new file mode 100644 index 0000000..9676de0 --- /dev/null +++ b/library/Businessprocess/Web/Form/Validator/NoDuplicateChildrenValidator.php @@ -0,0 +1,57 @@ +<?php + +namespace Icinga\Module\Businessprocess\Web\Form\Validator; + +use Icinga\Module\Businessprocess\BpConfig; +use Icinga\Module\Businessprocess\BpNode; +use Icinga\Module\Businessprocess\Forms\EditNodeForm; +use Icinga\Module\Businessprocess\Web\Form\QuickForm; +use Zend_Validate_Abstract; + +class NoDuplicateChildrenValidator extends Zend_Validate_Abstract +{ + const CHILD_FOUND = 'childFound'; + + /** @var QuickForm */ + protected $form; + + /** @var BpConfig */ + protected $bp; + + /** @var BpNode */ + protected $parent; + + /** @var string */ + protected $label; + + public function __construct(QuickForm $form, BpConfig $bp, BpNode $parent = null) + { + $this->form = $form; + $this->bp = $bp; + $this->parent = $parent; + + $this->_messageVariables['label'] = 'label'; + $this->_messageTemplates = [ + self::CHILD_FOUND => mt('businessprocess', '%label% is already defined in this process') + ]; + } + + public function isValid($value) + { + if ($this->parent === null) { + $found = $this->bp->hasRootNode($value); + } elseif ($this->form instanceof EditNodeForm && $this->form->getNode()->getName() === $value) { + $found = false; + } else { + $found = $this->parent->hasChild($value); + } + + if (! $found) { + return true; + } + + $this->label = $this->form->getElement('children')->getMultiOptions()[$value]; + $this->_error(self::CHILD_FOUND); + return false; + } +} |