summaryrefslogtreecommitdiffstats
path: root/library/Businessprocess/Web
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Businessprocess/Web/Component/ActionBar.php15
-rw-r--r--library/Businessprocess/Web/Component/BpDashboardTile.php47
-rw-r--r--library/Businessprocess/Web/Component/Content.php14
-rw-r--r--library/Businessprocess/Web/Component/Controls.php14
-rw-r--r--library/Businessprocess/Web/Component/Dashboard.php140
-rw-r--r--library/Businessprocess/Web/Component/DashboardAction.php27
-rw-r--r--library/Businessprocess/Web/Component/RenderedProcessActionBar.php161
-rw-r--r--library/Businessprocess/Web/Component/Tabs.php9
-rw-r--r--library/Businessprocess/Web/Component/WtfTabs.php22
-rw-r--r--library/Businessprocess/Web/Controller.php262
-rw-r--r--library/Businessprocess/Web/FakeRequest.php26
-rw-r--r--library/Businessprocess/Web/Form/BpConfigBaseForm.php135
-rw-r--r--library/Businessprocess/Web/Form/CsrfToken.php53
-rw-r--r--library/Businessprocess/Web/Form/Element/Checkbox.php8
-rw-r--r--library/Businessprocess/Web/Form/Element/FormElement.php9
-rw-r--r--library/Businessprocess/Web/Form/Element/IplStateOverrides.php75
-rw-r--r--library/Businessprocess/Web/Form/Element/SimpleNote.php22
-rw-r--r--library/Businessprocess/Web/Form/FormLoader.php39
-rw-r--r--library/Businessprocess/Web/Form/QuickBaseForm.php166
-rw-r--r--library/Businessprocess/Web/Form/QuickForm.php514
-rw-r--r--library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php96
-rw-r--r--library/Businessprocess/Web/Navigation/Renderer/ProcessProblemsBadge.php59
-rw-r--r--library/Businessprocess/Web/Navigation/Renderer/ProcessesProblemsBadge.php53
-rw-r--r--library/Businessprocess/Web/Url.php32
24 files changed, 1998 insertions, 0 deletions
diff --git a/library/Businessprocess/Web/Component/ActionBar.php b/library/Businessprocess/Web/Component/ActionBar.php
new file mode 100644
index 0000000..94458dc
--- /dev/null
+++ b/library/Businessprocess/Web/Component/ActionBar.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use ipl\Html\BaseHtmlElement;
+
+class ActionBar extends BaseHtmlElement
+{
+ protected $contentSeparator = ' ';
+
+ /** @var string */
+ protected $tag = 'div';
+
+ protected $defaultAttributes = array('class' => 'action-bar');
+}
diff --git a/library/Businessprocess/Web/Component/BpDashboardTile.php b/library/Businessprocess/Web/Component/BpDashboardTile.php
new file mode 100644
index 0000000..9a4a0f6
--- /dev/null
+++ b/library/Businessprocess/Web/Component/BpDashboardTile.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\Text;
+use ipl\Web\Url;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\Link;
+
+class BpDashboardTile extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $defaultAttributes = ['class' => 'dashboard-tile'];
+
+ public function __construct(BpConfig $bp, $title, $description, $icon, $url, $urlParams = null, $attributes = null)
+ {
+ $this->add(Html::tag(
+ 'div',
+ ['class' => 'bp-link', 'data-base-target' => '_main'],
+ (new Link(new Icon($icon), Url::fromPath($url, $urlParams ?: []), $attributes))
+ ->add(Html::tag('span', ['class' => 'header'], $title))
+ ->add($description)
+ ));
+
+ $tiles = Html::tag('div', ['class' => 'bp-root-tiles']);
+
+ foreach ($bp->getChildren() as $node) {
+ $state = strtolower($node->getStateName());
+
+ $tiles->add(Html::tag(
+ 'a',
+ [
+ 'href' => Url::fromPath($url, $urlParams ?: [])->with(['node' => $node->getName()]),
+ 'class' => "badge state-{$state}",
+ 'title' => $node->getAlias()
+ ],
+ Text::create('&nbsp;')->setEscaped()
+ ));
+ }
+
+ $this->add($tiles);
+ }
+}
diff --git a/library/Businessprocess/Web/Component/Content.php b/library/Businessprocess/Web/Component/Content.php
new file mode 100644
index 0000000..6d14197
--- /dev/null
+++ b/library/Businessprocess/Web/Component/Content.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use ipl\Html\BaseHtmlElement;
+
+class Content extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = array('class' => 'content');
+}
diff --git a/library/Businessprocess/Web/Component/Controls.php b/library/Businessprocess/Web/Component/Controls.php
new file mode 100644
index 0000000..259cbbb
--- /dev/null
+++ b/library/Businessprocess/Web/Component/Controls.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use ipl\Html\BaseHtmlElement;
+
+class Controls extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = array('class' => 'controls');
+}
diff --git a/library/Businessprocess/Web/Component/Dashboard.php b/library/Businessprocess/Web/Component/Dashboard.php
new file mode 100644
index 0000000..d211772
--- /dev/null
+++ b/library/Businessprocess/Web/Component/Dashboard.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use Exception;
+use Icinga\Application\Modules\Module;
+use Icinga\Authentication\Auth;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
+use Icinga\Module\Businessprocess\State\IcingaDbState;
+use Icinga\Module\Businessprocess\State\MonitoringState;
+use Icinga\Module\Businessprocess\Storage\Storage;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+class Dashboard extends BaseHtmlElement
+{
+ /** @var string */
+ protected $contentSeparator = "\n";
+
+ /** @var string */
+ protected $tag = 'div';
+
+ protected $defaultAttributes = array(
+ 'class' => 'overview-dashboard',
+ 'data-base-target' => '_next'
+ );
+
+ /** @var Auth */
+ protected $auth;
+
+ /** @var Storage */
+ protected $storage;
+
+ /**
+ * Dashboard constructor.
+ * @param Auth $auth
+ * @param Storage $storage
+ */
+ protected function __construct(Auth $auth, Storage $storage)
+ {
+ $this->auth = $auth;
+ $this->storage = $storage;
+ // TODO: Auth?
+ $processes = $storage->listProcessNames();
+ $this->add(
+ Html::tag('h1', null, mt('businessprocess', 'Welcome to your Business Process Overview'))
+ );
+ $this->add(Html::tag(
+ 'p',
+ null,
+ mt(
+ 'businessprocess',
+ 'From here you can reach all your defined Business Process'
+ . ' configurations, create new or modify existing ones'
+ )
+ ));
+ if ($auth->hasPermission('businessprocess/create')) {
+ $this->add(
+ new DashboardAction(
+ mt('businessprocess', 'Create'),
+ mt('businessprocess', 'Create a new Business Process configuration'),
+ 'plus',
+ 'businessprocess/process/create',
+ null,
+ array('class' => 'addnew')
+ )
+ )->add(
+ new DashboardAction(
+ mt('businessprocess', 'Upload'),
+ mt('businessprocess', 'Upload an existing Business Process configuration'),
+ 'upload',
+ 'businessprocess/process/upload',
+ null,
+ array('class' => 'addnew')
+ )
+ );
+ } elseif (empty($processes)) {
+ $this->add(
+ Html::tag('div')
+ ->add(Html::tag('h1', null, mt('businessprocess', 'Not available')))
+ ->add(Html::tag('p', null, mt(
+ 'businessprocess',
+ 'No Business Process has been defined for you'
+ )))
+ );
+ }
+
+ foreach ($processes as $name) {
+ $meta = $storage->loadMetadata($name);
+ $title = $meta->get('Title');
+
+ if ($title === null) {
+ $title = $name;
+ }
+
+ try {
+ $bp = $storage->loadProcess($name);
+ } catch (Exception $e) {
+ $this->add(new BpDashboardTile(
+ new BpConfig(),
+ $title,
+ sprintf(t('File %s has faulty config'), $name . '.conf'),
+ 'file-circle-xmark',
+ 'businessprocess/process/show',
+ ['config' => $name]
+ ));
+
+ continue;
+ }
+
+ if (Module::exists('icingadb') &&
+ (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())
+ ) {
+ IcingaDbState::apply($bp);
+ } else {
+ MonitoringState::apply($bp);
+ }
+
+ $this->add(new BpDashboardTile(
+ $bp,
+ $title,
+ $meta->get('Description'),
+ 'sitemap',
+ 'businessprocess/process/show',
+ array('config' => $name)
+ ));
+ }
+ }
+
+ /**
+ * @param Auth $auth
+ * @param Storage $storage
+ * @return static
+ */
+ public static function create(Auth $auth, Storage $storage)
+ {
+ return new static($auth, $storage);
+ }
+}
diff --git a/library/Businessprocess/Web/Component/DashboardAction.php b/library/Businessprocess/Web/Component/DashboardAction.php
new file mode 100644
index 0000000..9bd3240
--- /dev/null
+++ b/library/Businessprocess/Web/Component/DashboardAction.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use Icinga\Web\Url;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Web\Widget\Icon;
+
+class DashboardAction extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $defaultAttributes = array('class' => 'action');
+
+ public function __construct($title, $description, $icon, $url, $urlParams = null, $attributes = null)
+ {
+ if (! isset($attributes['href'])) {
+ $attributes['href'] = Url::fromPath($url, $urlParams ?: []);
+ }
+
+ $this->add(Html::tag('a', $attributes)
+ ->add(new Icon($icon))
+ ->add(Html::tag('span', ['class' => 'header'], $title))
+ ->add($description));
+ }
+}
diff --git a/library/Businessprocess/Web/Component/RenderedProcessActionBar.php b/library/Businessprocess/Web/Component/RenderedProcessActionBar.php
new file mode 100644
index 0000000..41fa0f8
--- /dev/null
+++ b/library/Businessprocess/Web/Component/RenderedProcessActionBar.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use Icinga\Authentication\Auth;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Renderer\Renderer;
+use Icinga\Module\Businessprocess\Renderer\TreeRenderer;
+use Icinga\Web\Url;
+use ipl\Html\Html;
+use ipl\Web\Widget\Icon;
+
+class RenderedProcessActionBar extends ActionBar
+{
+ public function __construct(BpConfig $config, Renderer $renderer, Url $url)
+ {
+ $meta = $config->getMetadata();
+
+ if ($renderer instanceof TreeRenderer) {
+ $link = Html::tag(
+ 'a',
+ [
+ 'href' => $url->with('mode', 'tile'),
+ 'title' => mt('businessprocess', 'Switch to Tile view')
+ ]
+ );
+ } else {
+ $link = Html::tag(
+ 'a',
+ [
+ 'href' => $url->with('mode', 'tree'),
+ 'title' => mt('businessprocess', 'Switch to Tree view')
+ ]
+ );
+ }
+
+ $link->add([
+ new Icon('grip', ['class' => $renderer instanceof TreeRenderer ? null : 'active']),
+ new Icon('sitemap', ['class' => $renderer instanceof TreeRenderer ? 'active' : null])
+ ]);
+
+ $this->add(
+ Html::tag('div', ['class' => 'view-toggle'])
+ ->add(Html::tag('span', null, mt('businessprocess', 'View')))
+ ->add($link)
+ );
+
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'data-base-target' => '_main',
+ 'href' => $url->with('showFullscreen', true),
+ 'title' => mt('businessprocess', 'Switch to fullscreen mode'),
+ ],
+ [
+ new Icon('maximize'),
+ mt('businessprocess', 'Fullscreen')
+ ]
+ ));
+
+ $hasChanges = $config->hasSimulations() || $config->hasBeenChanged();
+
+ if ($renderer->isLocked()) {
+ if (! $renderer->wantsRootNodes() && $renderer->rendersImportedNode()) {
+ $span = Html::tag('span', [
+ 'class' => 'disabled',
+ 'title' => mt(
+ 'businessprocess',
+ 'Imported processes can only be changed in their original configuration'
+ )
+ ]);
+ $span->add([new Icon('lock'), mt('businessprocess', 'Editing Locked')]);
+ $this->add($span);
+ } else {
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'href' => $url->with('unlocked', true),
+ 'title' => mt('businessprocess', 'Click to unlock editing for this process'),
+ ],
+ [
+ new Icon('lock'),
+ mt('businessprocess', 'Unlock Editing')
+ ]
+ ));
+ }
+ } elseif (! $hasChanges) {
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'href' => $url->without('unlocked')->without('action'),
+ 'title' => mt('businessprocess', 'Click to lock editing for this process'),
+ ],
+ [
+ new Icon('lock-open'),
+ mt('businessprocess', 'Lock Editing')
+ ]
+ ));
+ }
+
+ if (($hasChanges || ! $renderer->isLocked()) && $meta->canModify()) {
+ if ($renderer->wantsRootNodes()) {
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'data-base-target' => '_next',
+ 'href' => Url::fromPath('businessprocess/process/config', $this->currentProcessParams($url)),
+ 'title' => mt('businessprocess', 'Modify this process'),
+ ],
+ [
+ new Icon('wrench'),
+ mt('businessprocess', 'Config')
+ ]
+ ));
+ } else {
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'href' => $url->with([
+ 'action' => 'edit',
+ 'editnode' => $url->getParam('node')
+ ])->getAbsoluteUrl(),
+ 'title' => mt('businessprocess', 'Modify this process'),
+ ],
+ [
+ new Icon('wrench'),
+ mt('businessprocess', 'Config')
+ ]
+ ));
+ }
+ }
+
+ if (($hasChanges || (! $renderer->isLocked())) && $meta->canModify()) {
+ $this->add(Html::tag(
+ 'a',
+ [
+ 'href' => $url->with('action', 'add'),
+ 'title' => mt('businessprocess', 'Add a new business process node'),
+ 'class' => 'button-link'
+ ],
+ [
+ new Icon('plus'),
+ mt('businessprocess', 'Add Node')
+ ]
+ ));
+ }
+ }
+
+ protected function currentProcessParams(Url $url)
+ {
+ $urlParams = $url->getParams();
+ $params = array();
+ foreach (array('config', 'node') as $name) {
+ if ($value = $urlParams->get($name)) {
+ $params[$name] = $value;
+ }
+ }
+
+ return $params;
+ }
+}
diff --git a/library/Businessprocess/Web/Component/Tabs.php b/library/Businessprocess/Web/Component/Tabs.php
new file mode 100644
index 0000000..aaa444e
--- /dev/null
+++ b/library/Businessprocess/Web/Component/Tabs.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use ipl\Html\ValidHtml;
+
+class Tabs extends WtfTabs implements ValidHtml
+{
+}
diff --git a/library/Businessprocess/Web/Component/WtfTabs.php b/library/Businessprocess/Web/Component/WtfTabs.php
new file mode 100644
index 0000000..8f2250f
--- /dev/null
+++ b/library/Businessprocess/Web/Component/WtfTabs.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Component;
+
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Class WtfTabs
+ *
+ * TODO: Please remove this as soon as we drop support for PHP 5.3.x
+ * This works around https://bugs.php.net/bug.php?id=43200 and fixes
+ * https://github.com/Icinga/icingaweb2-module-businessprocess/issues/81
+ *
+ * @package Icinga\Module\Businessprocess\Web\Component
+ */
+class WtfTabs extends Tabs
+{
+ public function render()
+ {
+ return parent::render();
+ }
+}
diff --git a/library/Businessprocess/Web/Controller.php b/library/Businessprocess/Web/Controller.php
new file mode 100644
index 0000000..43200cc
--- /dev/null
+++ b/library/Businessprocess/Web/Controller.php
@@ -0,0 +1,262 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Modification\ProcessChanges;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+use Icinga\Module\Businessprocess\Storage\Storage;
+use Icinga\Module\Businessprocess\Web\Component\ActionBar;
+use Icinga\Module\Businessprocess\Web\Component\Controls;
+use Icinga\Module\Businessprocess\Web\Component\Content;
+use Icinga\Module\Businessprocess\Web\Component\Tabs;
+use Icinga\Module\Businessprocess\Web\Form\FormLoader;
+use Icinga\Web\Notification;
+use Icinga\Web\View;
+use ipl\Html\Html;
+use ipl\Web\Compat\CompatController;
+
+class Controller extends CompatController
+{
+ /** @var View */
+ public $view;
+
+ /** @var BpConfig */
+ protected $bp;
+
+ /** @var Tabs */
+ protected $mytabs;
+
+ /** @var Storage */
+ private $storage;
+
+ /** @var bool */
+ protected $showFullscreen;
+
+ /** @var Url */
+ private $url;
+
+ public function init()
+ {
+ $m = Icinga::app()->getModuleManager();
+ if (! $m->hasLoaded('monitoring') && $m->hasInstalled('monitoring')) {
+ $m->loadModule('monitoring');
+ }
+ $this->controls();
+ $this->content();
+ $this->url();
+ $this->view->showFullscreen
+ = $this->showFullscreen
+ = (bool) $this->_helper->layout()->showFullscreen;
+
+ $this->setViewScript('default');
+ }
+
+ /**
+ * @return Url
+ */
+ protected function url()
+ {
+ if ($this->url === null) {
+ $this->url = Url::fromPath(
+ $this->getRequest()->getUrl()->getPath()
+ )->setParams($this->getRequest()->getUrl()->getParams());
+ }
+
+ return $this->url;
+ }
+
+ /**
+ * @return ActionBar
+ */
+ protected function actions()
+ {
+ if ($this->view->actions === null) {
+ $this->view->actions = new ActionBar();
+ }
+
+ return $this->view->actions;
+ }
+
+ /**
+ * @return Controls
+ */
+ protected function controls()
+ {
+ if ($this->view->controls === null) {
+ $controls = $this->view->controls = new Controls();
+ if ($this->view->compact) {
+ $controls->getAttributes()->add('class', 'compact');
+ }
+ }
+
+ return $this->view->controls;
+ }
+
+ /**
+ * @return Content
+ */
+ protected function content()
+ {
+ if ($this->view->content === null) {
+ $content = $this->view->content = new Content();
+ if ($this->view->compact) {
+ $content->getAttributes()->add('class', 'compact');
+ }
+ }
+
+ return $this->view->content;
+ }
+
+ /**
+ * @param $label
+ * @return Tabs
+ */
+ protected function singleTab($label)
+ {
+ return $this->tabs()->add(
+ 'tab',
+ array(
+ 'label' => $label,
+ 'url' => $this->getRequest()->getUrl()
+ )
+ )->activate('tab');
+ }
+
+ /**
+ * @return Tabs
+ */
+ protected function defaultTab()
+ {
+ return $this->singleTab($this->translate('Business Process'));
+ }
+
+ /**
+ * @return Tabs
+ */
+ protected function overviewTab()
+ {
+ return $this->tabs()->add(
+ 'overview',
+ array(
+ 'label' => $this->translate('Business Process'),
+ 'url' => 'businessprocess'
+ )
+ )->activate('overview');
+ }
+
+ /**
+ * @return Tabs
+ */
+ protected function tabs()
+ {
+ // Todo: do not add to view once all of them render controls()
+ if ($this->mytabs === null) {
+ $tabs = new Tabs();
+ //$this->controls()->add($tabs);
+ $this->mytabs = $tabs;
+ }
+
+ return $this->mytabs;
+ }
+
+ protected function session()
+ {
+ return $this->Window()->getSessionNamespace('businessprocess');
+ }
+
+ protected function setViewScript($name)
+ {
+ $this->_helper->viewRenderer->setNoController(true);
+ $this->_helper->viewRenderer->setScriptAction($name);
+ return $this;
+ }
+
+ protected function addTitle($title)
+ {
+ $args = func_get_args();
+ array_shift($args);
+ $this->view->title = vsprintf($title, $args);
+ $this->controls()->add(Html::tag('h1', null, $this->view->title));
+ return $this;
+ }
+
+ protected function loadModifiedBpConfig()
+ {
+ $bp = $this->loadBpConfig();
+ $changes = ProcessChanges::construct($bp, $this->session());
+ if ($this->params->get('dismissChanges')) {
+ Notification::success(
+ sprintf(
+ $this->translate('%d pending change(s) have been dropped'),
+ $changes->count()
+ )
+ );
+ $changes->clear();
+ $this->redirectNow($this->url()->without('dismissChanges')->without('unlocked'));
+ }
+ $bp->applyChanges($changes);
+ return $bp;
+ }
+
+ protected function doNotRender()
+ {
+ $this->_helper->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+ return $this;
+ }
+
+ protected function loadBpConfig()
+ {
+ $name = $this->params->get('config');
+ /** @var LegacyStorage $storage */
+ $storage = $this->storage();
+
+ if (! $storage->hasProcess($name)) {
+ $this->httpNotFound(
+ $this->translate('No such process config: "%s"'),
+ $name
+ );
+ }
+
+ $modifications = $this->session()->get('modifications', array());
+ if (array_key_exists($name, $modifications)) {
+ $bp = $storage->loadFromString($name, $modifications[$name]);
+ } else {
+ $bp = $storage->loadProcess($name);
+ }
+
+ // allow URL parameter to override configured state type
+ if (null !== ($stateType = $this->params->get('state_type'))) {
+ if ($stateType === 'soft') {
+ $bp->useSoftStates();
+ }
+ if ($stateType === 'hard') {
+ $bp->useHardStates();
+ }
+ }
+
+ $this->view->bpconfig = $this->bp = $bp;
+ $this->view->configName = $bp->getName();
+
+ return $bp;
+ }
+
+ public function loadForm($name)
+ {
+ return FormLoader::load($name, $this->Module());
+ }
+
+ /**
+ * @return LegacyStorage
+ */
+ protected function storage()
+ {
+ if ($this->storage === null) {
+ $this->storage = LegacyStorage::getInstance();
+ }
+
+ return $this->storage;
+ }
+}
diff --git a/library/Businessprocess/Web/FakeRequest.php b/library/Businessprocess/Web/FakeRequest.php
new file mode 100644
index 0000000..4e54117
--- /dev/null
+++ b/library/Businessprocess/Web/FakeRequest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web;
+
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Request;
+
+class FakeRequest extends Request
+{
+ /** @var string */
+ private static $baseUrl;
+
+ public static function setConfiguredBaseUrl($url)
+ {
+ self::$baseUrl = $url;
+ }
+
+ public function getBaseUrl($raw = false)
+ {
+ if (self::$baseUrl === null) {
+ throw new ProgrammingError('Cannot determine base URL on CLI if not configured');
+ } else {
+ return self::$baseUrl;
+ }
+ }
+}
diff --git a/library/Businessprocess/Web/Form/BpConfigBaseForm.php b/library/Businessprocess/Web/Form/BpConfigBaseForm.php
new file mode 100644
index 0000000..5ccdf06
--- /dev/null
+++ b/library/Businessprocess/Web/Form/BpConfigBaseForm.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Form;
+
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Authentication\Auth;
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\Storage\Storage;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Web\Session\SessionNamespace;
+use ipl\Sql\Connection as IcingaDbConnection;
+
+abstract class BpConfigBaseForm extends QuickForm
+{
+ /** @var Storage */
+ protected $storage;
+
+ /** @var BpConfig */
+ protected $bp;
+
+ /** @var MonitoringBackend|IcingaDbConnection*/
+ protected $backend;
+
+ /** @var SessionNamespace */
+ protected $session;
+
+ 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;
+ }
+
+ /**
+ * Set the storage to use
+ *
+ * @param Storage $storage
+ *
+ * @return $this
+ */
+ public function setStorage(Storage $storage): self
+ {
+ $this->storage = $storage;
+
+ return $this;
+ }
+
+ /**
+ * Set the config to use
+ *
+ * @param BpConfig $config
+ *
+ * @return $this
+ */
+ public function setProcess(BpConfig $config): self
+ {
+ $this->bp = $config;
+ $this->setBackend($config->getBackend());
+
+ return $this;
+ }
+
+ /**
+ * Set the backend to use
+ *
+ * @param MonitoringBackend|IcingaDbConnection $backend
+ *
+ * @return $this
+ */
+ public function setBackend($backend): self
+ {
+ $this->backend = $backend;
+
+ return $this;
+ }
+
+ /**
+ * Set the session namespace to use
+ *
+ * @param SessionNamespace $session
+ *
+ * @return $this
+ */
+ public function setSession(SessionNamespace $session): self
+ {
+ $this->session = $session;
+
+ 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;
+ }
+
+ protected function setPreferredDecorators()
+ {
+ parent::setPreferredDecorators();
+
+ $this->setAttrib('class', $this->getAttrib('class') . ' bp-form');
+
+ return $this;
+ }
+}
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/IplStateOverrides.php b/library/Businessprocess/Web/Form/Element/IplStateOverrides.php
new file mode 100644
index 0000000..5b9ea16
--- /dev/null
+++ b/library/Businessprocess/Web/Form/Element/IplStateOverrides.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Form\Element;
+
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\FieldsetElement;
+
+class IplStateOverrides extends FieldsetElement
+{
+ /** @var array */
+ protected $options = [];
+
+ /**
+ * Set the options show
+ *
+ * @param array $options
+ *
+ * @return $this
+ */
+ public function setOptions(array $options): self
+ {
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Get the options to show
+ *
+ * @return array
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ public function getValues()
+ {
+ $cleanedValue = parent::getValues();
+
+ if (! empty($cleanedValue)) {
+ foreach ($cleanedValue as $from => $to) {
+ if ((int) $from === (int) $to) {
+ unset($cleanedValue[$from]);
+ }
+ }
+ }
+
+ return $cleanedValue;
+ }
+
+ protected function assemble()
+ {
+ $states = $this->getOptions();
+ foreach ($states as $state => $label) {
+ if ($state === 0) {
+ continue;
+ }
+
+ $this->addElement('select', $state, [
+ 'label' => $label,
+ 'value' => $state,
+ 'options' => [$state => $this->translate('Keep actual state')] + $states
+ ]);
+ }
+ }
+
+ protected function registerAttributeCallbacks(Attributes $attributes)
+ {
+ parent::registerAttributeCallbacks($attributes);
+
+ $this->getAttributes()
+ ->registerAttributeCallback('options', null, [$this, 'setOptions']);
+ }
+}
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/FormLoader.php b/library/Businessprocess/Web/Form/FormLoader.php
new file mode 100644
index 0000000..0cc5389
--- /dev/null
+++ b/library/Businessprocess/Web/Form/FormLoader.php
@@ -0,0 +1,39 @@
+<?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\\';
+ }
+
+ $file = null;
+ 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..36d134f
--- /dev/null
+++ b/library/Businessprocess/Web/Form/QuickBaseForm.php
@@ -0,0 +1,166 @@
+<?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)) {
+ $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..cb4d287
--- /dev/null
+++ b/library/Businessprocess/Web/Form/QuickForm.php
@@ -0,0 +1,514 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Form;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Web;
+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()
+ {
+ }
+
+ /**
+ * @param $action string|Url
+ * @return $this
+ */
+ 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 Web $app */
+ $app = Icinga::app();
+ /** @var Response $response */
+ $response = $app->getFrontController()->getResponse();
+ $response->redirectAndExit($url);
+ }
+
+ protected function setHttpResponseCode($code)
+ {
+ /** @var Web $app */
+ $app = Icinga::app();
+ $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 Web $app */
+ $app = Icinga::app();
+ /** @var Request $request */
+ $request = $app->getFrontController()->getRequest();
+ $this->setRequest($request);
+ }
+ return $this->request;
+ }
+
+ public function hasBeenSent()
+ {
+ if ($this->hasBeenSent === null) {
+ if ($this->request === null) {
+ /** @var Web $app */
+ $app = Icinga::app();
+ $req = $app->getFrontController()->getRequest();
+ } else {
+ $req = $this->request;
+ }
+
+ /** @var Request $req */
+ 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/HostServiceTermValidator.php b/library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php
new file mode 100644
index 0000000..58249f7
--- /dev/null
+++ b/library/Businessprocess/Web/Form/Validator/HostServiceTermValidator.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Form\Validator;
+
+use Icinga\Module\Businessprocess\BpConfig;
+use Icinga\Module\Businessprocess\BpNode;
+use Icinga\Module\Businessprocess\ServiceNode;
+use Icinga\Module\Businessprocess\State\IcingaDbState;
+use Icinga\Module\Businessprocess\State\MonitoringState;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use ipl\I18n\Translation;
+use ipl\Validator\BaseValidator;
+use ipl\Web\FormElement\TermInput\Term;
+use LogicException;
+
+class HostServiceTermValidator extends BaseValidator
+{
+ use Translation;
+
+ /** @var ?BpNode */
+ protected $parent;
+
+ /**
+ * Set the affected process
+ *
+ * @param BpNode $parent
+ *
+ * @return $this
+ */
+ public function setParent(BpNode $parent): self
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ public function isValid($terms)
+ {
+ if ($this->parent === null) {
+ throw new LogicException('Missing parent process. Cannot validate terms.');
+ }
+
+ if (! is_array($terms)) {
+ $terms = [$terms];
+ }
+
+ $isValid = true;
+ $testConfig = new BpConfig();
+
+ foreach ($terms as $term) {
+ /** @var Term $term */
+ [$hostName, $serviceName] = BpConfig::splitNodeName($term->getSearchValue());
+ if ($serviceName !== null && $serviceName !== 'Hoststatus') {
+ $node = $testConfig->createService($hostName, $serviceName);
+ } else {
+ $node = $testConfig->createHost($hostName);
+ if ($serviceName === null) {
+ $term->setSearchValue(BpConfig::joinNodeName($hostName, 'Hoststatus'));
+ }
+ }
+
+ if ($this->parent->hasChild($term->getSearchValue())) {
+ $term->setMessage($this->translate('Already defined in this process'));
+ $isValid = false;
+ } else {
+ $testConfig->getNode('__unbound__')
+ ->addChild($node);
+ }
+ }
+
+ if ($this->parent->getBpConfig()->getBackend() instanceof MonitoringBackend) {
+ MonitoringState::apply($testConfig);
+ } else {
+ IcingaDbState::apply($testConfig);
+ }
+
+ foreach ($terms as $term) {
+ /** @var Term $term */
+ $node = $testConfig->getNode($term->getSearchValue());
+ if ($node->isMissing()) {
+ if ($node instanceof ServiceNode) {
+ $term->setMessage($this->translate('Service not found'));
+ } else {
+ $term->setMessage($this->translate('Host not found'));
+ }
+
+ $isValid = false;
+ } else {
+ $term->setLabel($node->getAlias());
+ $term->setClass($node->getObjectClassName());
+ }
+ }
+
+ return $isValid;
+ }
+}
diff --git a/library/Businessprocess/Web/Navigation/Renderer/ProcessProblemsBadge.php b/library/Businessprocess/Web/Navigation/Renderer/ProcessProblemsBadge.php
new file mode 100644
index 0000000..575dc5e
--- /dev/null
+++ b/library/Businessprocess/Web/Navigation/Renderer/ProcessProblemsBadge.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Navigation\Renderer;
+
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
+
+class ProcessProblemsBadge extends BadgeNavigationItemRenderer
+{
+ /**
+ * Cached count
+ *
+ * @var int
+ */
+ protected $count;
+
+ /** @var string */
+ private $bpConfigName;
+
+ public function getCount()
+ {
+ $count = 0;
+ if ($this->count === null) {
+ $storage = LegacyStorage::getInstance();
+ $bp = $storage->loadProcess($this->getBpConfigName());
+ foreach ($bp->getRootNodes() as $rootNode) {
+ if (! $rootNode->isEmpty() &&
+ $rootNode->getState() !== $rootNode::ICINGA_PENDING
+ && $rootNode->hasProblems()) {
+ $count++;
+ }
+ }
+
+ $this->count = $count;
+ $this->setState(self::STATE_CRITICAL);
+ }
+
+ if ($count) {
+ $this->setTitle(sprintf(
+ tp('One unhandled root node critical', '%d unhandled root nodes critical', $count),
+ $count
+ ));
+ }
+
+ return $this->count;
+ }
+
+ public function setBpConfigName($bpConfigName)
+ {
+ $this->bpConfigName = $bpConfigName;
+
+ return $this;
+ }
+
+ public function getBpConfigName()
+ {
+ return $this->bpConfigName;
+ }
+}
diff --git a/library/Businessprocess/Web/Navigation/Renderer/ProcessesProblemsBadge.php b/library/Businessprocess/Web/Navigation/Renderer/ProcessesProblemsBadge.php
new file mode 100644
index 0000000..dd419a2
--- /dev/null
+++ b/library/Businessprocess/Web/Navigation/Renderer/ProcessesProblemsBadge.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web\Navigation\Renderer;
+
+use Icinga\Application\Modules\Module;
+use Icinga\Module\Businessprocess\ProvidedHook\Icingadb\IcingadbSupport;
+use Icinga\Module\Businessprocess\State\IcingaDbState;
+use Icinga\Module\Businessprocess\State\MonitoringState;
+use Icinga\Module\Businessprocess\Storage\LegacyStorage;
+use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
+
+class ProcessesProblemsBadge extends BadgeNavigationItemRenderer
+{
+ /**
+ * Cached count
+ *
+ * @var int
+ */
+ protected $count;
+
+ public function getCount()
+ {
+ if ($this->count === null) {
+ $storage = LegacyStorage::getInstance();
+ $count = 0;
+
+ foreach ($storage->listProcessNames() as $processName) {
+ $bp = $storage->loadProcess($processName);
+ if (Module::exists('icingadb') &&
+ (! $bp->hasBackendName() && IcingadbSupport::useIcingaDbAsBackend())
+ ) {
+ IcingaDbState::apply($bp);
+ } else {
+ MonitoringState::apply($bp);
+ }
+
+ foreach ($bp->getRootNodes() as $rootNode) {
+ if (! $rootNode->isEmpty() &&
+ $rootNode->getState() !== $rootNode::ICINGA_PENDING
+ && $rootNode->hasProblems()) {
+ $count++;
+ break;
+ }
+ }
+ }
+
+ $this->count = $count;
+ $this->setState(self::STATE_CRITICAL);
+ }
+
+ return $this->count;
+ }
+}
diff --git a/library/Businessprocess/Web/Url.php b/library/Businessprocess/Web/Url.php
new file mode 100644
index 0000000..92b1e85
--- /dev/null
+++ b/library/Businessprocess/Web/Url.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Icinga\Module\Businessprocess\Web;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Web;
+use Icinga\Web\Request;
+use Icinga\Web\Url as WebUrl;
+
+/**
+ * Class Url
+ *
+ * The main purpose of this class is to get unit tests running on CLI
+ *
+ * @package Icinga\Module\Businessprocess\Web
+ */
+class Url extends WebUrl
+{
+ /**
+ * @return FakeRequest|Request
+ */
+ protected static function getRequest()
+ {
+ $app = Icinga::app();
+ if ($app->isCli()) {
+ return new FakeRequest();
+ }
+
+ /** @var Web $app */
+ return $app->getRequest();
+ }
+}