summaryrefslogtreecommitdiffstats
path: root/vendor/gipfl/icingaweb2/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
commit3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch)
treeee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/icingaweb2/src
parentInitial commit. (diff)
downloadicingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.tar.xz
icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.zip
Adding upstream version 0.20.0.upstream/0.20.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/icingaweb2/src')
-rw-r--r--vendor/gipfl/icingaweb2/src/CompatController.php581
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php30
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php46
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php173
-rw-r--r--vendor/gipfl/icingaweb2/src/Data/Paginatable.php64
-rw-r--r--vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php68
-rw-r--r--vendor/gipfl/icingaweb2/src/FakeRequest.php32
-rw-r--r--vendor/gipfl/icingaweb2/src/Icon.php27
-rw-r--r--vendor/gipfl/icingaweb2/src/IconHelper.php89
-rw-r--r--vendor/gipfl/icingaweb2/src/Img.php83
-rw-r--r--vendor/gipfl/icingaweb2/src/Link.php85
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php29
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php74
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php263
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php281
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php34
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php149
-rw-r--r--vendor/gipfl/icingaweb2/src/Translator.php26
-rw-r--r--vendor/gipfl/icingaweb2/src/Url.php162
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ActionBar.php25
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Content.php14
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Controls.php163
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php60
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ListItem.php26
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php29
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Paginator.php463
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Tabs.php44
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php93
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php335
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php111
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php115
31 files changed, 3774 insertions, 0 deletions
diff --git a/vendor/gipfl/icingaweb2/src/CompatController.php b/vendor/gipfl/icingaweb2/src/CompatController.php
new file mode 100644
index 0000000..952b4b5
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/CompatController.php
@@ -0,0 +1,581 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use gipfl\IcingaWeb2\Controller\Extension\AutoRefreshHelper;
+use gipfl\IcingaWeb2\Controller\Extension\ConfigHelper;
+use gipfl\Translation\StaticTranslator;
+use gipfl\Translation\TranslationHelper;
+use gipfl\IcingaWeb2\Controller\Extension\ControlsAndContentHelper;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Zf1\SimpleViewRenderer;
+use GuzzleHttp\Psr7\ServerRequest;
+use Icinga\Application\Benchmark;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Manager;
+use Icinga\Application\Modules\Module;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\ProgrammingError;
+use Icinga\File\Pdf;
+use Icinga\Security\SecurityException;
+use Icinga\User;
+use Icinga\Web\Notification;
+use Icinga\Web\Session;
+use Icinga\Web\UrlParams;
+use Icinga\Web\Url as WebUrl;
+use Icinga\Web\Window;
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use RuntimeException;
+use Zend_Controller_Action;
+use Zend_Controller_Action_HelperBroker as ZfHelperBroker;
+use Zend_Controller_Request_Abstract as ZfRequest;
+use Zend_Controller_Response_Abstract as ZfResponse;
+
+/**
+ * Class CompatController
+ * @method \Icinga\Web\Request getRequest() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Request
+ * }
+ *
+ * @method \Icinga\Web\Response getResponse() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Response
+ * }
+ */
+class CompatController extends Zend_Controller_Action implements ControlsAndContent
+{
+ use AutoRefreshHelper;
+ use ControlsAndContentHelper;
+ use ConfigHelper;
+ use TranslationHelper;
+
+ /** @var bool Whether the controller requires the user to be authenticated */
+ protected $requiresAuthentication = true;
+
+ protected $applicationName = 'Icinga Web';
+
+ /** @var string The current module's name */
+ private $moduleName;
+
+ private $module;
+
+ private $window;
+
+ // https://github.com/joshbuchea/HEAD
+
+ /** @var SimpleViewRenderer */
+ protected $viewRenderer;
+
+ /** @var bool */
+ private $reloadCss = false;
+
+ /** @var bool */
+ private $rerenderLayout = false;
+
+ /** @var string */
+ private $xhrLayout = 'inline';
+
+ /** @var \Zend_Layout */
+ private $layout;
+
+ /** @var string The inner layout (inside the body) to use */
+ private $innerLayout = 'body';
+
+ /**
+ * Authentication manager
+ *
+ * @var Auth|null
+ */
+ private $auth;
+
+ /** @var UrlParams */
+ protected $params;
+
+ /**
+ * The constructor starts benchmarking, loads the configuration and sets
+ * other useful controller properties
+ *
+ * @param ZfRequest $request
+ * @param ZfResponse $response
+ * @param array $invokeArgs Any additional invocation arguments
+ * @throws SecurityException
+ */
+ public function __construct(
+ ZfRequest $request,
+ ZfResponse $response,
+ array $invokeArgs = array()
+ ) {
+ /** @var \Icinga\Web\Request $request */
+ /** @var \Icinga\Web\Response $response */
+ $this->setRequest($request)
+ ->setResponse($response)
+ ->_setInvokeArgs($invokeArgs);
+
+ $this->prepareViewRenderer();
+ $this->_helper = new ZfHelperBroker($this);
+
+ $this->handlerBrowserWindows();
+ $this->initializeTranslator();
+ $this->initializeLayout();
+
+ $this->view->compact = $request->getParam('view') === 'compact';
+ $url = $this->url();
+ $this->params = $url->getParams();
+
+ if ($url->shift('showCompact')) {
+ $this->view->compact = true;
+ }
+
+ $this->checkPermissionBasics();
+ Benchmark::measure('Ready to initialize the controller');
+ $this->prepareInit();
+ $this->init();
+ }
+
+ protected function initializeLayout()
+ {
+ $url = $this->url();
+ $layout = $this->layout = $this->_helper->layout();
+ $layout->isIframe = $url->shift('isIframe');
+ $layout->showFullscreen = $url->shift('showFullscreen');
+ $layout->moduleName = $this->getModuleName();
+ if ($this->rerenderLayout = $url->shift('renderLayout')) {
+ $this->xhrLayout = $this->innerLayout;
+ }
+ if ($url->shift('_disableLayout')) {
+ $this->layout->disableLayout();
+ }
+ }
+
+ /**
+ * Prepare controller initialization
+ *
+ * As it should not be required for controllers to call the parent's init() method,
+ * base controllers should use prepareInit() in order to prepare the controller
+ * initialization.
+ *
+ * @see \Zend_Controller_Action::init() For the controller initialization method.
+ */
+ protected function prepareInit()
+ {
+ }
+
+ /**
+ * Return the current module's name
+ *
+ * @return string
+ */
+ public function getModuleName()
+ {
+ if ($this->moduleName === null) {
+ $this->moduleName = $this->getRequest()->getModuleName();
+ }
+
+ return $this->moduleName;
+ }
+
+ protected function setModuleName($name)
+ {
+ $this->moduleName = $name;
+
+ return $this;
+ }
+
+ /**
+ * Return this controller's module
+ *
+ * @return Module
+ * @codingStandardsIgnoreStart
+ */
+ public function Module()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->module === null) {
+ try {
+ $this->module = Icinga::app()->getModuleManager()->getModule($this->getModuleName());
+ } catch (ProgrammingError $e) {
+ throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ return $this->module;
+ }
+
+ /**
+ * @return Window
+ * @codingStandardsIgnoreStart
+ */
+ public function Window()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->window === null) {
+ $this->window = new Window(
+ $this->getRequestHeader('X-Icinga-WindowId', Window::UNDEFINED)
+ );
+ }
+ return $this->window;
+ }
+
+ protected function handlerBrowserWindows()
+ {
+ if ($this->isXhr()) {
+ $id = $this->getRequestHeader('X-Icinga-WindowId', Window::UNDEFINED);
+
+ if ($id === Window::UNDEFINED) {
+ $this->window = new Window($id);
+ $this->_response->setHeader('X-Icinga-WindowId', Window::generateId());
+ }
+ }
+ }
+
+ /**
+ * @return ServerRequestInterface
+ */
+ protected function getServerRequest()
+ {
+ return ServerRequest::fromGlobals();
+ }
+
+ protected function getRequestHeader($key, $default = null)
+ {
+ try {
+ $value = $this->getRequest()->getHeader($key);
+ } catch (\Zend_Controller_Request_Exception $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ if ($value === null) {
+ return $default;
+ } else {
+ return $value;
+ }
+ }
+
+ /**
+ * @throws SecurityException
+ */
+ protected function checkPermissionBasics()
+ {
+ $request = $this->getRequest();
+ // $auth->authenticate($request, $response, $this->requiresLogin());
+ if ($this->requiresLogin()) {
+ if (! $request->isXmlHttpRequest() && $request->isApiRequest()) {
+ Auth::getInstance()->challengeHttp();
+ }
+ $this->redirectToLogin(Url::fromRequest());
+ }
+ if (($this->Auth()->isAuthenticated() || $this->requiresLogin())
+ && $this->getFrontController()->getDefaultModule() !== $this->getModuleName()) {
+ $this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->getModuleName());
+ }
+ }
+
+ protected function initializeTranslator()
+ {
+ $moduleName = $this->getModuleName();
+ $domain = $moduleName !== 'default' ? $moduleName : 'icinga';
+ $this->view->translationDomain = $domain;
+ StaticTranslator::set(new Translator($domain));
+ }
+
+ public function init()
+ {
+ // Hint: we intentionally do not call our parent's init() method
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return Auth
+ * @codingStandardsIgnoreStart
+ */
+ public function Auth()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->auth === null) {
+ $this->auth = Auth::getInstance();
+ }
+ return $this->auth;
+ }
+
+ /**
+ * Whether the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @return bool
+ */
+ public function hasPermission($permission)
+ {
+ return $this->Auth()->hasPermission($permission);
+ }
+
+ /**
+ * Assert that the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @throws SecurityException If the current user lacks the given permission
+ */
+ public function assertPermission($permission)
+ {
+ if (! $this->Auth()->hasPermission($permission)) {
+ throw new SecurityException('No permission for %s', $permission);
+ }
+ }
+
+ /**
+ * Return restriction information for an eventually authenticated user
+ *
+ * @param string $name Restriction name
+ *
+ * @return array
+ */
+ public function getRestrictions($name)
+ {
+ return $this->Auth()->getRestrictions($name);
+ }
+
+ /**
+ * Check whether the controller requires a login. That is when the controller requires authentication and the
+ * user is currently not authenticated
+ *
+ * @return bool
+ */
+ protected function requiresLogin()
+ {
+ if (! $this->requiresAuthentication) {
+ return false;
+ }
+
+ return ! $this->Auth()->isAuthenticated();
+ }
+
+ public function prepareViewRenderer()
+ {
+ $this->viewRenderer = new SimpleViewRenderer();
+ $this->viewRenderer->replaceZendViewRenderer();
+ $this->view = $this->viewRenderer->view;
+ }
+
+ /**
+ * @return SimpleViewRenderer
+ */
+ public function getViewRenderer()
+ {
+ return $this->viewRenderer;
+ }
+
+ protected function redirectXhr($url)
+ {
+ if (! $url instanceof WebUrl) {
+ $url = Url::fromPath($url);
+ }
+
+ if ($this->rerenderLayout) {
+ $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
+ }
+ if ($this->reloadCss) {
+ $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
+ }
+
+ $this->shutdownSession();
+
+ $this->getResponse()
+ ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
+ ->sendHeaders();
+
+ exit;
+ }
+
+ /**
+ * Detect whether the current request requires changes in the layout and apply them before rendering
+ *
+ * @see Zend_Controller_Action::postDispatch()
+ */
+ public function postDispatch()
+ {
+ Benchmark::measure('Action::postDispatch()');
+
+ $layout = $this->layout;
+ $req = $this->getRequest();
+ $layout->innerLayout = $this->innerLayout;
+
+ /** @var User $user */
+ if ($user = $req->getUser()) {
+ if ((bool) $user->getPreferences()->getValue('icingaweb', 'show_benchmark', false)) {
+ if ($layout->isEnabled()) {
+ $layout->benchmark = $this->renderBenchmark();
+ }
+ }
+
+ if (! (bool) $user->getPreferences()->getValue('icingaweb', 'auto_refresh', true)) {
+ $this->disableAutoRefresh();
+ }
+ }
+
+ if ($req->getParam('format') === 'pdf') {
+ $this->shutdownSession();
+ $this->sendAsPdf();
+ return;
+ }
+
+ if ($this->isXhr()) {
+ $this->postDispatchXhr();
+ }
+
+ $this->shutdownSession();
+ }
+
+ public function postDispatchXhr()
+ {
+ $this->layout->setLayout($this->xhrLayout);
+ $resp = $this->getResponse();
+
+ $notifications = Notification::getInstance();
+ if ($notifications->hasMessages()) {
+ $notificationList = array();
+ foreach ($notifications->popMessages() as $m) {
+ $notificationList[] = rawurlencode($m->type . ' ' . $m->message);
+ }
+ $resp->setHeader('X-Icinga-Notification', implode('&', $notificationList), true);
+ }
+
+ if ($this->reloadCss) {
+ $resp->setHeader('X-Icinga-CssReload', 'now', true);
+ }
+
+ if ($this->title) {
+ if (preg_match('~[\r\n]~', $this->title)) {
+ // TODO: Innocent exception and error log for hack attempts
+ throw new InvalidArgumentException('No newlines allowed in page title');
+ }
+ $resp->setHeader(
+ 'X-Icinga-Title',
+ // TODO: Config
+ rawurlencode($this->title . ' :: ' . $this->applicationName),
+ true
+ );
+ } else {
+ // TODO: config
+ $resp->setHeader('X-Icinga-Title', rawurlencode($this->applicationName), true);
+ }
+
+ if ($this->rerenderLayout) {
+ $this->getResponse()->setHeader('X-Icinga-Container', 'layout', true);
+ }
+
+ if (isset($this->autorefreshInterval)) {
+ $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval, true);
+ }
+
+ if ($name = $this->getModuleName()) {
+ $this->getResponse()->setHeader('X-Icinga-Module', $name, true);
+ }
+ }
+
+ /**
+ * Redirect to login
+ *
+ * XHR will always redirect to __SELF__ if an URL to redirect to after successful login is set. __SELF__ instructs
+ * JavaScript to redirect to the current window's URL if it's an auto-refresh request or to redirect to the URL
+ * which required login if it's not an auto-refreshing one.
+ *
+ * XHR will respond with HTTP status code 403 Forbidden.
+ *
+ * @param Url|string $redirect URL to redirect to after successful login
+ */
+ protected function redirectToLogin($redirect = null)
+ {
+ $login = Url::fromPath('authentication/login');
+ if ($this->isXhr()) {
+ if ($redirect !== null) {
+ $login->setParam('redirect', '__SELF__');
+ }
+
+ $this->_response->setHttpResponseCode(403);
+ } elseif ($redirect !== null) {
+ if (! $redirect instanceof Url) {
+ $redirect = Url::fromPath($redirect);
+ }
+
+ if (($relativeUrl = $redirect->getRelativeUrl())) {
+ $login->setParam('redirect', $relativeUrl);
+ }
+ }
+
+ $this->rerenderLayout()->redirectNow($login);
+ }
+
+ protected function sendAsPdf()
+ {
+ $pdf = new Pdf();
+ $pdf->renderControllerAction($this);
+ }
+
+ /**
+ * Render the benchmark
+ *
+ * @return string Benchmark HTML
+ */
+ protected function renderBenchmark()
+ {
+ $this->viewRenderer->postDispatch();
+ Benchmark::measure('Response ready');
+ return Benchmark::renderToHtml()/*
+ . '<pre style="height: 16em; vertical-overflow: scroll">'
+ . print_r(get_included_files(), 1)
+ . '</pre>'*/;
+ }
+
+ public function isXhr()
+ {
+ return $this->getRequest()->isXmlHttpRequest();
+ }
+
+ protected function redirectHttp($url)
+ {
+ if (! $url instanceof Url) {
+ $url = Url::fromPath($url);
+ }
+ $this->shutdownSession();
+ $this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
+ }
+
+ /**
+ * Redirect to a specific url, updating the browsers URL field
+ *
+ * @param Url|string $url The target to redirect to
+ **/
+ public function redirectNow($url)
+ {
+ if ($this->isXhr()) {
+ $this->redirectXhr($url);
+ } else {
+ $this->redirectHttp($url);
+ }
+ }
+
+ protected function shutdownSession()
+ {
+ $session = Session::getSession();
+ if ($session->hasChanged()) {
+ $session->write();
+ }
+ }
+
+ protected function rerenderLayout()
+ {
+ $this->rerenderLayout = true;
+ $this->xhrLayout = 'layout';
+ return $this;
+ }
+
+ protected function reloadCss()
+ {
+ $this->reloadCss = true;
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php
new file mode 100644
index 0000000..5b899ba
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use InvalidArgumentException;
+
+trait AutoRefreshHelper
+{
+ /** @var int|null */
+ private $autorefreshInterval;
+
+ public function setAutorefreshInterval($interval)
+ {
+ if (! is_int($interval) || $interval < 1) {
+ throw new InvalidArgumentException(
+ 'Setting autorefresh interval smaller than 1 second is not allowed'
+ );
+ }
+ $this->autorefreshInterval = $interval;
+ $this->layout->autorefreshInterval = $interval;
+ return $this;
+ }
+
+ public function disableAutoRefresh()
+ {
+ $this->autorefreshInterval = null;
+ $this->layout->autorefreshInterval = null;
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php
new file mode 100644
index 0000000..72cefa7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use Icinga\Application\Config;
+
+trait ConfigHelper
+{
+ private $config;
+
+ private $configs = [];
+
+ /**
+ * @param null $file
+ * @return Config
+ * @codingStandardsIgnoreStart
+ */
+ public function Config($file = null)
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->moduleName === null) {
+ if ($file === null) {
+ return Config::app();
+ } else {
+ return Config::app($file);
+ }
+ } else {
+ return $this->getModuleConfig($file);
+ }
+ }
+
+ public function getModuleConfig($file = null)
+ {
+ if ($file === null) {
+ if ($this->config === null) {
+ $this->config = Config::module($this->getModuleName());
+ }
+ return $this->config;
+ } else {
+ if (! array_key_exists($file, $this->configs)) {
+ $this->configs[$file] = Config::module($this->getModuleName(), $file);
+ }
+ return $this->configs[$file];
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php
new file mode 100644
index 0000000..00ef389
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\Content;
+use gipfl\IcingaWeb2\Widget\Controls;
+use gipfl\IcingaWeb2\Widget\Tabs;
+use ipl\Html\HtmlDocument;
+
+trait ControlsAndContentHelper
+{
+ /** @var Controls */
+ private $controls;
+
+ /** @var Content */
+ private $content;
+
+ protected $title;
+
+ /** @var Url */
+ private $url;
+
+ /** @var Url */
+ private $originalUrl;
+
+ /**
+ * TODO: Not sure whether we need dedicated Content/Controls classes,
+ * a simple Container with a class name might suffice here
+ *
+ * @return Controls
+ */
+ public function controls()
+ {
+ if ($this->controls === null) {
+ $this->view->controls = $this->controls = new Controls();
+ }
+
+ return $this->controls;
+ }
+
+ /**
+ * @param Tabs|null $tabs
+ * @return Tabs
+ */
+ public function tabs(Tabs $tabs = null)
+ {
+ if ($tabs === null) {
+ return $this->controls()->getTabs();
+ } else {
+ $this->controls()->setTabs($tabs);
+ return $tabs;
+ }
+ }
+
+ /**
+ * @param HtmlDocument|null $actionBar
+ * @return HtmlDocument
+ */
+ public function actions(HtmlDocument $actionBar = null)
+ {
+ if ($actionBar === null) {
+ return $this->controls()->getActionBar();
+ } else {
+ $this->controls()->setActionBar($actionBar);
+ return $actionBar;
+ }
+ }
+
+ /**
+ * @return Content
+ */
+ public function content()
+ {
+ if ($this->content === null) {
+ $this->view->content = $this->content = new Content();
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $this->makeTitle(func_get_args());
+ return $this;
+ }
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function addTitle($title)
+ {
+ $title = $this->makeTitle(func_get_args());
+ $this->title = $title;
+ $this->controls()->addTitle($title);
+
+ return $this;
+ }
+
+ private function makeTitle($args)
+ {
+ $title = array_shift($args);
+
+ if (empty($args)) {
+ return $title;
+ } else {
+ return vsprintf($title, $args);
+ }
+ }
+
+ /**
+ * @param string $title
+ * @param mixed $url
+ * @param string $name
+ * @return $this
+ */
+ public function addSingleTab($title, $url = null, $name = 'main')
+ {
+ if ($url === null) {
+ $url = $this->url();
+ }
+
+ $this->tabs()->add($name, [
+ 'label' => $title,
+ 'url' => $url,
+ ])->activate($name);
+
+ return $this;
+ }
+
+ /**
+ * @return Url
+ */
+ public function url()
+ {
+ if ($this->url === null) {
+ $this->url = $this->getOriginalUrl();
+ }
+
+ return $this->url;
+ }
+
+ /**
+ * @return Url
+ */
+ public function getOriginalUrl()
+ {
+ if ($this->originalUrl === null) {
+ $this->originalUrl = clone($this->getUrlFromRequest());
+ }
+
+ return clone($this->originalUrl);
+ }
+
+ /**
+ * @return Url
+ */
+ protected function getUrlFromRequest()
+ {
+ /** @var \Icinga\Web\Request $request */
+ $request = $this->getRequest();
+ $webUrl = $request->getUrl();
+
+ return Url::fromPath(
+ $webUrl->getPath()
+ )->setParams($webUrl->getParams());
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Data/Paginatable.php b/vendor/gipfl/icingaweb2/src/Data/Paginatable.php
new file mode 100644
index 0000000..8cd3963
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Data/Paginatable.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Data;
+
+use Countable;
+
+interface Paginatable extends Countable
+{
+ /**
+ * Set a limit count and offset
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Skip that many rows
+ *
+ * @return self
+ */
+ public function limit($count = null, $offset = null);
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit();
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit();
+
+ /**
+ * Set limit
+ *
+ * @param int $limit Number of rows to return
+ *
+ * @return int|null
+ */
+ public function setLimit($limit);
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset();
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset();
+
+ /**
+ * Set offset
+ *
+ * @param int $offset Skip that many rows
+ *
+ * @return int|null
+ */
+ public function setOffset($offset);
+}
diff --git a/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php b/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php
new file mode 100644
index 0000000..d6a1b97
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Data;
+
+use Icinga\Application\Benchmark;
+use Icinga\Data\SimpleQuery;
+
+class SimpleQueryPaginationAdapter implements Paginatable
+{
+ /** @var SimpleQuery */
+ private $query;
+
+ public function __construct(SimpleQuery $query)
+ {
+ $this->query = $query;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ Benchmark::measure('Running count() for pagination');
+ $count = $this->query->count();
+ Benchmark::measure("Counted $count rows");
+
+ return $count;
+ }
+
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ }
+
+ public function hasLimit()
+ {
+ return $this->getLimit() !== null;
+ }
+
+ public function getLimit()
+ {
+ return $this->query->getLimit();
+ }
+
+ public function setLimit($limit)
+ {
+ $this->query->limit(
+ $limit,
+ $this->getOffset()
+ );
+ }
+
+ public function hasOffset()
+ {
+ return $this->getOffset() !== null;
+ }
+
+ public function getOffset()
+ {
+ return $this->query->getOffset();
+ }
+
+ public function setOffset($offset)
+ {
+ $this->query->limit(
+ $this->getLimit(),
+ $offset
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/FakeRequest.php b/vendor/gipfl/icingaweb2/src/FakeRequest.php
new file mode 100644
index 0000000..854c26e
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/FakeRequest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Request;
+use RuntimeException;
+
+class FakeRequest extends Request
+{
+ /** @var string */
+ private static $baseUrl;
+
+ public static function setConfiguredBaseUrl($url)
+ {
+ self::$baseUrl = $url;
+ }
+
+ public function setUrl(Url $url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ public function getBaseUrl($raw = false)
+ {
+ if (self::$baseUrl === null) {
+ throw new RuntimeException('Cannot determine base URL on CLI if not configured');
+ } else {
+ return self::$baseUrl;
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Icon.php b/vendor/gipfl/icingaweb2/src/Icon.php
new file mode 100644
index 0000000..0001e12
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Icon.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use ipl\Html\BaseHtmlElement;
+
+class Icon extends BaseHtmlElement
+{
+ protected $tag = 'i';
+
+ public function __construct($name, $attributes = null)
+ {
+ $this->setAttributes($attributes);
+ $this->getAttributes()->add('class', array('icon', 'icon-' . $name));
+ }
+
+ /**
+ * @param string $name
+ * @param array $attributes
+ *
+ * @return static
+ */
+ public static function create($name, array $attributes = null)
+ {
+ return new static($name, $attributes);
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/IconHelper.php b/vendor/gipfl/icingaweb2/src/IconHelper.php
new file mode 100644
index 0000000..1c8af9d
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/IconHelper.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use InvalidArgumentException;
+
+/**
+ * Icon helper class
+ *
+ * Should help to reduce redundant icon-lookup code. Currently with hardcoded
+ * icons only, could easily provide support for all of them as follows:
+ *
+ * $confFile = Icinga::app()
+ * ->getApplicationDir('fonts/fontello-ifont/config.json');
+ *
+ * $font = json_decode(file_get_contents($confFile));
+ * // 'icon-' is to be found in $font->css_prefix_text
+ * foreach ($font->glyphs as $icon) {
+ * // $icon->css (= 'help') -> 0x . dechex($icon->code)
+ * }
+ */
+class IconHelper
+{
+ private $icons = [
+ 'minus' => 'e806',
+ 'trash' => 'e846',
+ 'plus' => 'e805',
+ 'cancel' => 'e804',
+ 'help' => 'e85b',
+ 'angle-double-right' => 'e87b',
+ 'up-big' => 'e825',
+ 'down-big' => 'e828',
+ 'down-open' => 'e821',
+ ];
+
+ private $mappedUtf8Icons;
+
+ private $reversedUtf8Icons;
+
+ private static $instance;
+
+ public function __construct()
+ {
+ $this->prepareIconMappings();
+ }
+
+ public static function instance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new static;
+ }
+
+ return self::$instance;
+ }
+
+ public function characterIconName($character)
+ {
+ if (array_key_exists($character, $this->reversedUtf8Icons)) {
+ return $this->reversedUtf8Icons[$character];
+ } else {
+ throw new InvalidArgumentException('There is no mapping for the given character');
+ }
+ }
+
+ protected function hexToCharacter($hex)
+ {
+ return json_decode('"\u' . $hex . '"');
+ }
+
+ public function iconCharacter($name)
+ {
+ if (array_key_exists($name, $this->mappedUtf8Icons)) {
+ return $this->mappedUtf8Icons[$name];
+ } else {
+ return $this->mappedUtf8Icons['help'];
+ }
+ }
+
+ protected function prepareIconMappings()
+ {
+ $this->mappedUtf8Icons = [];
+ $this->reversedUtf8Icons = [];
+ foreach ($this->icons as $name => $hex) {
+ $character = $this->hexToCharacter($hex);
+ $this->mappedUtf8Icons[$name] = $character;
+ $this->reversedUtf8Icons[$character] = $name;
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Img.php b/vendor/gipfl/icingaweb2/src/Img.php
new file mode 100644
index 0000000..3c68adb
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Img.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Url as WebUrl;
+use ipl\Html\Attribute;
+use ipl\Html\BaseHtmlElement;
+
+class Img extends BaseHtmlElement
+{
+ protected $tag = 'img';
+
+ /** @var Url */
+ protected $url;
+
+ protected $defaultAttributes = array('alt' => '');
+
+ protected function __construct()
+ {
+ }
+
+ /**
+ * @param Url|string $url
+ * @param array $urlParams
+ * @param array $attributes
+ *
+ * @return static
+ */
+ public static function create($url, $urlParams = null, array $attributes = null)
+ {
+ /** @var Img $img */
+ $img = new static();
+ $img->setAttributes($attributes);
+ $img->getAttributes()->registerAttributeCallback('src', array($img, 'getSrcAttribute'));
+ $img->setUrl($url, $urlParams);
+ return $img;
+ }
+
+ public function setUrl($url, $urlParams)
+ {
+ if ($url instanceof WebUrl) { // Hint: Url is also a WebUrl
+ if ($urlParams !== null) {
+ $url->addParams($urlParams);
+ }
+
+ $this->url = $url;
+ } else {
+ if ($urlParams === null) {
+ if (is_string($url) && substr($url, 0, 5) === 'data:') {
+ $this->url = $url;
+ return;
+ } else {
+ $this->url = Url::fromPath($url);
+ }
+ } else {
+ $this->url = Url::fromPath($url, $urlParams);
+ }
+ }
+
+ $this->url->getParams();
+ }
+
+ /**
+ * @return Attribute
+ */
+ public function getSrcAttribute()
+ {
+ if (is_string($this->url)) {
+ return new Attribute('src', $this->url);
+ } else {
+ return new Attribute('src', $this->getUrl()->getAbsoluteUrl('&'));
+ }
+ }
+
+ /**
+ * @return Url
+ */
+ public function getUrl()
+ {
+ // TODO: What if null? #?
+ return $this->url;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Link.php b/vendor/gipfl/icingaweb2/src/Link.php
new file mode 100644
index 0000000..e6e4de9
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Link.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Url as WebUrl;
+use ipl\Html\Attribute;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\ValidHtml;
+
+class Link extends BaseHtmlElement
+{
+ protected $tag = 'a';
+
+ /** @var Url */
+ protected $url;
+
+ /**
+ * Link constructor.
+ * @param $content
+ * @param $url
+ * @param null $urlParams
+ * @param array|null $attributes
+ */
+ public function __construct($content, $url, $urlParams = null, array $attributes = null)
+ {
+ $this->setContent($content);
+ $this->setAttributes($attributes);
+ $this->getAttributes()->registerAttributeCallback('href', array($this, 'getHrefAttribute'));
+ $this->setUrl($url, $urlParams);
+ }
+
+ /**
+ * @param ValidHtml|array|string $content
+ * @param Url|string $url
+ * @param array $urlParams
+ * @param mixed $attributes
+ *
+ * @return static
+ */
+ public static function create($content, $url, $urlParams = null, array $attributes = null)
+ {
+ $link = new static($content, $url, $urlParams, $attributes);
+ return $link;
+ }
+
+ /**
+ * @param $url
+ * @param $urlParams
+ */
+ public function setUrl($url, $urlParams)
+ {
+ if ($url instanceof WebUrl) { // Hint: Url is also a WebUrl
+ if ($urlParams !== null) {
+ $url->addParams($urlParams);
+ }
+
+ $this->url = $url;
+ } else {
+ if ($urlParams === null) {
+ $this->url = Url::fromPath($url);
+ } else {
+ $this->url = Url::fromPath($url, $urlParams);
+ }
+ }
+
+ $this->url->getParams();
+ }
+
+ /**
+ * @return Attribute
+ */
+ public function getHrefAttribute()
+ {
+ return new Attribute('href', $this->getUrl()->getAbsoluteUrl('&'));
+ }
+
+ /**
+ * @return Url
+ */
+ public function getUrl()
+ {
+ // TODO: What if null? #?
+ return $this->url;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php
new file mode 100644
index 0000000..7a5a3ff
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Url;
+
+// Could also be a static method, MultiSelect::enable($table)
+trait MultiSelect
+{
+ protected function enableMultiSelect($url, $sourceUrl, array $keys)
+ {
+ /** @var $table \ipl\Html\BaseHtmlElement */
+ $table = $this;
+ $table->addAttributes([
+ 'class' => 'multiselect'
+ ]);
+
+ $prefix = 'data-icinga-multiselect';
+ $multi = [
+ "$prefix-url" => Url::fromPath($url),
+ "$prefix-controllers" => Url::fromPath($sourceUrl),
+ "$prefix-data" => implode(',', $keys),
+ ];
+
+ $table->addAttributes($multi);
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php
new file mode 100644
index 0000000..0f0cec3
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\Controls;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+trait QuickSearch
+{
+ /** @var BaseHtmlElement */
+ private $quickSearchForm;
+
+ public function getQuickSearch(BaseHtmlElement $parent, Url $url)
+ {
+ $this->requireQuickSearchForm($parent, $url);
+ $search = $url->getParam('q');
+ return $search;
+ }
+
+ private function requireQuickSearchForm(BaseHtmlElement $parent, Url $url)
+ {
+ if ($this->quickSearchForm === null) {
+ $this->quickSearchForm = $this->buildQuickSearchForm($parent, $url);
+ }
+ }
+
+ private function buildQuickSearchForm(BaseHtmlElement $parent, Url $url)
+ {
+ $search = $url->getParam('q');
+
+ $form = Html::tag('form', [
+ 'action' => $url->without(array('q', 'page', 'modifyFilter'))->getAbsoluteUrl(),
+ 'class' => ['gipfl-quicksearch'],
+ 'method' => 'GET'
+ ]);
+
+ $form->add(
+ Html::tag('input', [
+ 'type' => 'text',
+ 'name' => 'q',
+ 'title' => $this->translate('Search is simple! Try to combine multiple words'),
+ 'value' => $search,
+ 'placeholder' => $this->translate('Search...'),
+ 'class' => 'search'
+ ])
+ );
+
+ $this->addQuickSearchToControls($parent, $form);
+
+ return $form;
+ }
+
+ protected function addQuickSearchToControls(BaseHtmlElement $parent, BaseHtmlElement $form)
+ {
+ if ($parent instanceof Controls) {
+ $title = $parent->getTitleElement();
+ if ($title === null) {
+ $parent->prepend($form);
+ } else {
+ $input = $form->getFirst('input');
+ $form->remove($input);
+ $title->add($input);
+ $form->add($title);
+ $parent->setTitleElement($form);
+ }
+ } else {
+ $parent->prepend($form);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php
new file mode 100644
index 0000000..cb1eac6
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
+use gipfl\IcingaWeb2\IconHelper;
+use gipfl\ZfDb\Exception\SelectException;
+use gipfl\ZfDb\Select;
+use Icinga\Web\Request;
+use Icinga\Web\Response;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use RuntimeException;
+use Zend_Db_Select_Exception as ZfDbSelectException;
+
+/**
+ * Trait ZfSortablePriority
+ *
+ * Assumes to run in a ZfQueryBasedTable
+ */
+trait ZfSortablePriority
+{
+ /** @var Request */
+ protected $request;
+
+ /** @var Response */
+ protected $response;
+
+ public function handleSortPriorityActions(Request $request, Response $response)
+ {
+ $this->request = $request;
+ $this->response = $response;
+ return $this;
+ }
+
+ protected function reallyHandleSortPriorityActions()
+ {
+ $request = $this->request;
+
+ if ($request->isPost() && $this->hasBeenSent($request)) {
+ // $this->fixPriorities();
+ foreach (array_keys($request->getPost()) as $key) {
+ if (substr($key, 0, 8) === 'MOVE_UP_') {
+ $id = (int) substr($key, 8);
+ $this->moveRow($id, 'up');
+ }
+ if (substr($key, 0, 10) === 'MOVE_DOWN_') {
+ $id = (int) substr($key, 10);
+ $this->moveRow($id, 'down');
+ }
+ }
+ $this->response->redirectAndExit($request->getUrl());
+ }
+ }
+
+ protected function hasBeenSent(Request $request)
+ {
+ return $request->getPost('__FORM_NAME') === $this->getUniqueFormName();
+ }
+
+ protected function addSortPriorityButtons(BaseHtmlElement $tr, $row)
+ {
+ $tr->add(
+ Html::tag(
+ 'td',
+ null,
+ $this->createUpDownButtons($row->{$this->getKeyColumn()})
+ )
+ );
+
+ return $tr;
+ }
+
+ protected function getKeyColumn()
+ {
+ if (isset($this->keyColumn)) {
+ return $this->keyColumn;
+ } else {
+ throw new RuntimeException(
+ 'ZfSortablePriority requires keyColumn'
+ );
+ }
+ }
+
+ protected function getPriorityColumn()
+ {
+ if (isset($this->priorityColumn)) {
+ return $this->priorityColumn;
+ } else {
+ throw new RuntimeException(
+ 'ZfSortablePriority requires priorityColumn'
+ );
+ }
+ }
+
+ protected function getPriorityColumns()
+ {
+ return [
+ 'id' => $this->getKeyColumn(),
+ 'prio' => $this->getPriorityColumn()
+ ];
+ }
+
+ protected function moveRow($id, $direction)
+ {
+ /** @var $this ZfQueryBasedTable */
+ $db = $this->db();
+ /** @var $this ZfQueryBasedTable */
+ $query = $this->getQuery();
+ $tableParts = $this->getQueryPart(Select::FROM);
+ $alias = key($tableParts);
+ $table = $tableParts[$alias]['tableName'];
+
+ $whereParts = $this->getQueryPart(Select::WHERE);
+ unset($query);
+ if (empty($whereParts)) {
+ $where = '';
+ } else {
+ $where = ' AND ' . implode(' ', $whereParts);
+ }
+
+ $prioCol = $this->getPriorityColumn();
+ $keyCol = $this->getKeyColumn();
+ $myPrio = (int) $db->fetchOne(
+ $db->select()
+ ->from($table, $prioCol)
+ ->where("$keyCol = ?", $id)
+ );
+
+ $op = $direction === 'up' ? '<' : '>';
+ $sortDir = $direction === 'up' ? 'DESC' : 'ASC';
+ $query = $db->select()
+ ->from([$alias => $table], $this->getPriorityColumns())
+ ->where("$prioCol $op ?", $myPrio)
+ ->order("$prioCol $sortDir")
+ ->limit(1);
+
+ if (! empty($whereParts)) {
+ $query->where(implode(' ', $whereParts));
+ }
+
+ $next = $db->fetchRow($query);
+
+ if ($next) {
+ $sql = 'UPDATE %s %s'
+ . ' SET %s = CASE WHEN %s = %s THEN %d ELSE %d END'
+ . ' WHERE %s IN (%s, %s)';
+
+ $query = sprintf(
+ $sql,
+ $table,
+ $alias,
+ $prioCol,
+ $keyCol,
+ $id,
+ (int) $next->prio,
+ $myPrio,
+ $keyCol,
+ $id,
+ (int) $next->id
+ ) . $where;
+
+ $db->query($query);
+ }
+ }
+
+ protected function getSortPriorityTitle()
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+
+ return Html::tag(
+ 'span',
+ ['title' => $table->translate('Change priority')],
+ $table->translate('Prio')
+ );
+ }
+
+ protected function createUpDownButtons($key)
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+ $up = $this->createIconButton(
+ "MOVE_UP_$key",
+ 'up-big',
+ $table->translate('Move up (raise priority)')
+ );
+ $down = $this->createIconButton(
+ "MOVE_DOWN_$key",
+ 'down-big',
+ $table->translate('Move down (lower priority)')
+ );
+
+ if ($table->isOnFirstRow()) {
+ $up->getAttributes()->add('disabled', 'disabled');
+ }
+
+ if ($table->isOnLastRow()) {
+ $down->getAttributes()->add('disabled', 'disabled');
+ }
+
+ return [$down, $up];
+ }
+
+ protected function createIconButton($key, $icon, $title)
+ {
+ return Html::tag('input', [
+ 'type' => 'submit',
+ 'class' => 'icon-button',
+ 'name' => $key,
+ 'title' => $title,
+ 'value' => IconHelper::instance()->iconCharacter($icon)
+ ]);
+ }
+
+ protected function getUniqueFormName()
+ {
+ $parts = explode('\\', get_class($this));
+ return end($parts);
+ }
+
+ protected function renderWithSortableForm()
+ {
+ if ($this->request === null) {
+ return parent::render();
+ }
+ $this->reallyHandleSortPriorityActions();
+
+ $url = $this->request->getUrl();
+ // TODO: No margin for form
+ $form = Html::tag('form', [
+ 'action' => $url->getAbsoluteUrl(),
+ 'method' => 'POST'
+ ], [
+ Html::tag('input', [
+ 'type' => 'hidden',
+ 'name' => '__FORM_NAME',
+ 'value' => $this->getUniqueFormName()
+ ]),
+ new HtmlString(parent::render())
+ ]);
+
+ return $form->render();
+ }
+
+ protected function getQueryPart($part)
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+ /** @var Select|\Zend_Db_Select $query */
+ $query = $table->getQuery();
+ try {
+ return $query->getPart($part);
+ } catch (SelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ } catch (ZfDbSelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php
new file mode 100644
index 0000000..e9281c7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php
@@ -0,0 +1,281 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use Countable;
+use gipfl\Format\LocalDateFormat;
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer;
+use gipfl\IcingaWeb2\Table\Extension\QuickSearch;
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Widget\Paginator;
+use gipfl\Translation\TranslationHelper;
+use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
+use ipl\Html\Table;
+
+abstract class QueryBasedTable extends Table implements Countable
+{
+ use TranslationHelper;
+ use QuickSearch;
+
+ protected $defaultAttributes = [
+ 'class' => ['common-table', 'table-row-selectable'],
+ 'data-base-target' => '_next',
+ ];
+
+ private $fetchedRows;
+
+ private $firstRow;
+
+ private $lastRow = false;
+
+ private $rowNumber;
+
+ private $rowNumberOnPage;
+
+ protected $lastDay;
+
+ /** @var Paginator|null Will usually be defined at rendering time */
+ protected $paginator;
+
+ private $isUsEnglish;
+
+ private $dateFormatter;
+
+ protected $searchColumns = [];
+
+ /**
+ * @return Paginatable
+ */
+ abstract protected function getPaginationAdapter();
+
+ abstract public function getQuery();
+
+ public function getPaginator(Url $url)
+ {
+ return new Paginator(
+ $this->getPaginationAdapter(),
+ $url
+ );
+ }
+
+ public function count()
+ {
+ return $this->getPaginationAdapter()->count();
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ FilterRenderer::applyToQuery($filter, $this->getQuery());
+ return $this;
+ }
+
+ protected function getSearchColumns()
+ {
+ return $this->searchColumns;
+ }
+
+ public function search($search)
+ {
+ if (! empty($search)) {
+ $query = $this->getQuery();
+ $columns = $this->getSearchColumns();
+ if (strpos($search, ' ') === false) {
+ $filter = Filter::matchAny();
+ foreach ($columns as $column) {
+ $filter->addFilter(Filter::expression($column, '=', "*$search*"));
+ }
+ } else {
+ $filter = Filter::matchAll();
+ foreach (explode(' ', $search) as $s) {
+ $sub = Filter::matchAny();
+ foreach ($columns as $column) {
+ $sub->addFilter(Filter::expression($column, '=', "*$s*"));
+ }
+ $filter->addFilter($sub);
+ }
+ }
+
+ FilterRenderer::applyToQuery($filter, $query);
+ }
+
+ return $this;
+ }
+
+ abstract protected function prepareQuery();
+
+ public function renderContent()
+ {
+ $titleColumns = $this->renderTitleColumns();
+ if ($titleColumns) {
+ $this->getHeader()->add($titleColumns);
+ }
+ $this->fetchRows();
+
+ return parent::renderContent();
+ }
+
+ protected function renderTitleColumns()
+ {
+ // TODO: drop this
+ if (method_exists($this, 'getColumnsToBeRendered')) {
+ $columns = $this->getColumnsToBeRendered();
+ if (isset($columns) && count($columns)) {
+ return static::row($columns, null, 'th');
+ }
+ }
+
+ return null;
+ }
+
+ protected function splitByDay($timestamp)
+ {
+ $this->renderDayIfNew((int) $timestamp);
+ }
+
+ public function isOnFirstPage()
+ {
+ if ($this->paginator === null) {
+ // No paginator? Then there should be only a single page
+ return true;
+ }
+
+ return $this->paginator->getCurrentPage() === 1;
+ }
+
+ public function isOnFirstRow()
+ {
+ return $this->firstRow === true;
+ }
+
+ public function isOnLastRow()
+ {
+ return $this->lastRow === true;
+ }
+
+ protected function fetchRows()
+ {
+ $firstPage = $this->isOnFirstPage();
+ $this->rowNumberOnPage = 0;
+ $this->rowNumber = $this->getPaginationAdapter()->getOffset();
+ $lastRow = count($this);
+ foreach ($this->fetch() as $row) {
+ $this->rowNumber++;
+ $this->rowNumberOnPage++;
+ if (null === $this->firstRow) {
+ if ($firstPage) {
+ $this->firstRow = true;
+ } else {
+ $this->firstRow = false;
+ }
+ } elseif (true === $this->firstRow) {
+ $this->firstRow = false;
+ }
+ if ($lastRow === $this->rowNumber) {
+ $this->lastRow = true;
+ }
+ // Hint: do not fetch the body first, the row might want to replace it
+ $tr = $this->renderRow($row);
+ $this->add($tr);
+ }
+ }
+
+ protected function renderRow($row)
+ {
+ return $this::row([$row]);
+ }
+
+ /**
+ * @deprecated
+ * @return bool
+ */
+ protected function isUsEnglish()
+ {
+ if ($this->isUsEnglish === null) {
+ $this->isUsEnglish = in_array(setlocale(LC_ALL, 0), ['en_US', 'en_US.UTF-8', 'C']);
+ }
+
+ return $this->isUsEnglish;
+ }
+
+ /**
+ * @param int $timestamp
+ */
+ protected function renderDayIfNew($timestamp)
+ {
+ $day = $this->getDateFormatter()->getFullDay($timestamp);
+
+ if ($this->lastDay !== $day) {
+ $this->nextHeader()->add(
+ $this::th($day, [
+ 'colspan' => 2,
+ 'class' => 'table-header-day'
+ ])
+ );
+
+ $this->lastDay = $day;
+ $this->nextBody();
+ }
+ }
+
+ abstract protected function fetchQueryRows();
+
+ public function fetch()
+ {
+ $parts = explode('\\', get_class($this));
+ $name = end($parts);
+ Benchmark::measure("Fetching data for $name table");
+ $rows = $this->fetchQueryRows();
+ $this->fetchedRows = count($rows);
+ Benchmark::measure("Fetched $this->fetchedRows rows for $name table");
+
+ return $rows;
+ }
+
+ protected function initializeOptionalQuickSearch(ControlsAndContent $controller)
+ {
+ $columns = $this->getSearchColumns();
+ if (! empty($columns)) {
+ $this->search(
+ $this->getQuickSearch(
+ $controller->controls(),
+ $controller->url()
+ )
+ );
+ }
+ }
+
+ /**
+ * @param ControlsAndContent $controller
+ * @return $this
+ */
+ public function renderTo(ControlsAndContent $controller)
+ {
+ $url = $controller->url();
+ $c = $controller->content();
+ $this->paginator = $this->getPaginator($url);
+ $this->initializeOptionalQuickSearch($controller);
+ $controller->actions()->add($this->paginator);
+ $c->add($this);
+
+ // TODO: move elsewhere
+ if (method_exists($this, 'dumpSqlQuery')) {
+ if ($url->getParam('format') === 'sql') {
+ $c->prepend($this->dumpSqlQuery($url));
+ }
+ }
+
+ return $this;
+ }
+
+ protected function getDateFormatter()
+ {
+ if ($this->dateFormatter === null) {
+ $this->dateFormatter = new LocalDateFormat();
+ }
+
+ return $this->dateFormatter;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php
new file mode 100644
index 0000000..8d6015a
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use Icinga\Data\SimpleQuery;
+use gipfl\IcingaWeb2\Data\SimpleQueryPaginationAdapter;
+
+abstract class SimpleQueryBasedTable extends QueryBasedTable
+{
+ /** @var SimpleQuery */
+ private $query;
+
+ protected function getPaginationAdapter()
+ {
+ return new SimpleQueryPaginationAdapter($this->getQuery());
+ }
+
+ protected function fetchQueryRows()
+ {
+ return $this->query->fetchAll();
+ }
+
+ /**
+ * @return SimpleQuery
+ */
+ public function getQuery()
+ {
+ if ($this->query === null) {
+ $this->query = $this->prepareQuery();
+ }
+
+ return $this->query;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php
new file mode 100644
index 0000000..8a421d5
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer;
+use gipfl\IcingaWeb2\Zf1\Db\SelectPaginationAdapter;
+use gipfl\ZfDb\Adapter\Adapter as Db;
+use gipfl\ZfDb\Select as DbSelect;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Data\Filter\Filter;
+use ipl\Html\DeferredText;
+use ipl\Html\Html;
+use LogicException;
+use Zend_Db_Adapter_Abstract as DbAdapter;
+
+abstract class ZfQueryBasedTable extends QueryBasedTable
+{
+ /** @var ?DbConnection */
+ private $connection;
+
+ /** @var DbAdapter|Db */
+ private $db;
+
+ private $query;
+
+ private $paginationAdapter;
+
+ public function __construct($db)
+ {
+ if ($db instanceof Db || $db instanceof DbAdapter) {
+ $this->db = $db;
+ } elseif ($db instanceof DbConnection) {
+ $this->connection = $db;
+ $this->db = $db->getDbAdapter();
+ } else {
+ throw new LogicException(sprintf(
+ 'Unable to deal with %s db class',
+ get_class($db)
+ ));
+ }
+ }
+
+ public static function show(ControlsAndContent $controller, DbConnection $db)
+ {
+ $table = new static($db);
+ $table->renderTo($controller);
+ }
+
+ public function getCountQuery()
+ {
+ return $this->getPaginationAdapter()->getCountQuery();
+ }
+
+ protected function getPaginationAdapter()
+ {
+ if ($this->paginationAdapter === null) {
+ $this->paginationAdapter = new SelectPaginationAdapter($this->getQuery());
+ }
+
+ return $this->paginationAdapter;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ FilterRenderer::applyToQuery($filter, $this->getQuery());
+ return $this;
+ }
+
+ public function search($search)
+ {
+ if (! empty($search)) {
+ $query = $this->getQuery();
+ $columns = $this->getSearchColumns();
+ if (strpos($search, ' ') === false) {
+ $filter = Filter::matchAny();
+ foreach ($columns as $column) {
+ $filter->addFilter(Filter::expression($column, '=', "*$search*"));
+ }
+ } else {
+ $filter = Filter::matchAll();
+ foreach (explode(' ', $search) as $s) {
+ $sub = Filter::matchAny();
+ foreach ($columns as $column) {
+ $sub->addFilter(Filter::expression($column, '=', "*$s*"));
+ }
+ $filter->addFilter($sub);
+ }
+ }
+
+ FilterRenderer::applyToQuery($filter, $query);
+ }
+
+ return $this;
+ }
+
+ protected function fetchQueryRows()
+ {
+ return $this->db->fetchAll($this->getQuery());
+ }
+
+ /**
+ * @deprecated Might be null, we'll fade it out
+ * @return ?DbConnection
+ */
+ public function connection()
+ {
+ return $this->connection;
+ }
+
+ public function db()
+ {
+ return $this->db;
+ }
+
+ /**
+ * @return DbSelect|\Zend_Db_Select
+ */
+ public function getQuery()
+ {
+ if ($this->query === null) {
+ $this->query = $this->prepareQuery();
+ }
+
+ return $this->query;
+ }
+
+ public function dumpSqlQuery(Url $url)
+ {
+ $self = $this;
+ return Html::tag('div', ['class' => 'sql-dump'], [
+ Link::create('[ close ]', $url->without('format')),
+ Html::tag('h3', null, $this->translate('SQL Query')),
+ Html::tag('pre', null, new DeferredText(
+ function () use ($self) {
+ return wordwrap($self->getQuery());
+ }
+ )),
+ Html::tag('h3', null, $this->translate('Count Query')),
+ Html::tag('pre', null, new DeferredText(
+ function () use ($self) {
+ return wordwrap($self->getCountQuery());
+ }
+ )),
+ ]);
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Translator.php b/vendor/gipfl/icingaweb2/src/Translator.php
new file mode 100644
index 0000000..b1cb088
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Translator.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use gipfl\Translation\TranslatorInterface;
+
+class Translator implements TranslatorInterface
+{
+ /** @var string */
+ private $domain;
+
+ public function __construct($domain)
+ {
+ $this->domain = $domain;
+ }
+
+ public function translate($string)
+ {
+ $res = dgettext($this->domain, $string);
+ if ($res === $string && $this->domain !== 'icinga') {
+ return dgettext('icinga', $string);
+ }
+
+ return $res;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Url.php b/vendor/gipfl/icingaweb2/src/Url.php
new file mode 100644
index 0000000..2c6bf1f
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Url.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Url as WebUrl;
+use Icinga\Web\UrlParams;
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\UriInterface;
+use RuntimeException;
+
+/**
+ * Class Url
+ *
+ * The main purpose of this class is to get unit tests running on CLI
+ * Little code from Icinga\Web\Url has been duplicated, as neither fromPath()
+ * nor getRequest() can be extended in a meaningful way at the time of this
+ * writing
+ */
+class Url extends WebUrl
+{
+ /**
+ * @param string $url
+ * @param array $params
+ * @param null $request
+ * @return Url
+ */
+ public static function fromPath($url, array $params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ if (! \is_string($url)) {
+ throw new InvalidArgumentException(sprintf(
+ 'url "%s" is not a string',
+ \var_export($url, 1)
+ ));
+ }
+
+ $self = new static;
+
+ if ($url === '#') {
+ return $self->setPath($url);
+ }
+
+ $parts = \parse_url($url);
+
+ $self->setBasePath($request->getBaseUrl());
+ if (isset($parts['path'])) {
+ $self->setPath($parts['path']);
+ }
+
+ if (isset($parts['query'])) {
+ $params = UrlParams::fromQueryString($parts['query'])->mergeValues($params);
+ }
+
+ if (isset($parts['fragment'])) {
+ $self->setAnchor($parts['fragment']);
+ }
+
+ $self->setParams($params);
+ return $self;
+ }
+
+ public static function fromUri(UriInterface $uri)
+ {
+ $query = $uri->getQuery();
+ $path = $uri->getPath();
+ if (\strlen($query)) {
+ $path .= "?$query";
+ }
+
+ return static::fromPath($path);
+ }
+
+ public static function fromServerRequest(ServerRequestInterface $request)
+ {
+ return static::fromUri($request->getUri());
+ }
+
+ /**
+ * Create a new Url class representing the current request
+ *
+ * If $params are given, those will be added to the request's parameters
+ * and overwrite any existing parameters
+ *
+ * @param UrlParams|array $params Parameters that should additionally be considered for the url
+ * @param \Icinga\Web\Request $request A request to use instead of the default one
+ *
+ * @return Url
+ */
+ public static function fromRequest($params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ $url = new Url();
+ $url->setPath(\ltrim($request->getPathInfo(), '/'));
+ $request->getQuery();
+
+ // $urlParams = UrlParams::fromQueryString($request->getQuery());
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $urlParams = UrlParams::fromQueryString($_SERVER['QUERY_STRING']);
+ } else {
+ $urlParams = UrlParams::fromQueryString('');
+ foreach ($request->getQuery() as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ }
+
+ foreach ($params as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ $url->setParams($urlParams);
+ $url->setBasePath($request->getBaseUrl());
+
+ return $url;
+ }
+
+ public function setBasePath($basePath)
+ {
+ if (property_exists($this, 'basePath')) {
+ parent::setBasePath($basePath);
+ } else {
+ $this->setBaseUrl($basePath);
+ }
+
+ return $this;
+ }
+
+ public function setParams($params)
+ {
+ try {
+ return parent::setParams($params);
+ } catch (ProgrammingError $e) {
+ throw new InvalidArgumentException($e->getMessage(), 0, $e);
+ }
+ }
+
+ protected static function getRequest()
+ {
+ try {
+ $app = Icinga::app();
+ } catch (ProgrammingError $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+ if ($app->isCli()) {
+ try {
+ return new FakeRequest();
+ } catch (Exception $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+ } else {
+ return $app->getRequest();
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php b/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php
new file mode 100644
index 0000000..63e6c77
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+
+class ActionBar extends BaseHtmlElement
+{
+ protected $contentSeparator = ' ';
+
+ /** @var string */
+ protected $tag = 'div';
+
+ protected $defaultAttributes = ['class' => 'gipfl-action-bar'];
+
+ /**
+ * @param string $target
+ * @return $this
+ */
+ public function setBaseTarget($target)
+ {
+ $this->getAttributes()->set('data-base-target', $target);
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Content.php b/vendor/gipfl/icingaweb2/src/Widget/Content.php
new file mode 100644
index 0000000..92ea115
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Content.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+
+class Content extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = ['class' => 'content'];
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Controls.php b/vendor/gipfl/icingaweb2/src/Widget/Controls.php
new file mode 100644
index 0000000..cb52013
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Controls.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+
+class Controls extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = ['class' => 'controls'];
+
+ /** @var Tabs */
+ private $tabs;
+
+ /** @var ActionBar */
+ private $actions;
+
+ /** @var string */
+ private $title;
+
+ /** @var string */
+ private $subTitle;
+
+ /** @var BaseHtmlElement */
+ private $titleElement;
+
+ /**
+ * @param $title
+ * @param null $subTitle
+ * @return $this
+ */
+ public function addTitle($title, $subTitle = null)
+ {
+ $this->title = $title;
+ if ($subTitle !== null) {
+ $this->subTitle = $subTitle;
+ }
+
+ return $this->setTitleElement($this->renderTitleElement());
+ }
+
+ /**
+ * @param BaseHtmlElement $element
+ * @return $this
+ */
+ public function setTitleElement(BaseHtmlElement $element)
+ {
+ if ($this->titleElement !== null) {
+ $this->remove($this->titleElement);
+ }
+
+ $this->titleElement = $element;
+ $this->prepend($element);
+
+ return $this;
+ }
+
+ public function getTitleElement()
+ {
+ return $this->titleElement;
+ }
+
+ /**
+ * @return Tabs
+ */
+ public function getTabs()
+ {
+ if ($this->tabs === null) {
+ $this->tabs = new Tabs();
+ }
+
+ return $this->tabs;
+ }
+
+ /**
+ * @param Tabs $tabs
+ * @return $this
+ */
+ public function setTabs(Tabs $tabs)
+ {
+ $this->tabs = $tabs;
+ return $this;
+ }
+
+ /**
+ * @param Tabs $tabs
+ * @return $this
+ */
+ public function prependTabs(Tabs $tabs)
+ {
+ if ($this->tabs === null) {
+ $this->tabs = $tabs;
+ } else {
+ $current = $this->tabs->getTabs();
+ $this->tabs = $tabs;
+ foreach ($current as $name => $tab) {
+ $this->tabs->add($name, $tab);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return ActionBar
+ */
+ public function getActionBar()
+ {
+ if ($this->actions === null) {
+ $this->setActionBar(new ActionBar());
+ }
+
+ return $this->actions;
+ }
+
+ /**
+ * @param HtmlDocument $actionBar
+ * @return $this
+ */
+ public function setActionBar(HtmlDocument $actionBar)
+ {
+ if ($this->actions !== null) {
+ $this->remove($this->actions);
+ }
+
+ $this->actions = $actionBar;
+ $this->add($actionBar);
+
+ return $this;
+ }
+
+ /**
+ * @return BaseHtmlElement
+ */
+ protected function renderTitleElement()
+ {
+ $h1 = Html::tag('h1', null, $this->title);
+ if ($this->subTitle) {
+ $h1->setSeparator(' ')->add(
+ Html::tag('small', null, $this->subTitle)
+ );
+ }
+
+ return $h1;
+ }
+
+ /**
+ * @return string
+ */
+ public function renderContent()
+ {
+ if (null !== $this->tabs) {
+ $this->prepend($this->tabs);
+ }
+
+ return parent::renderContent();
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php b/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php
new file mode 100644
index 0000000..8574ce7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\HtmlDocument;
+use gipfl\IcingaWeb2\Url;
+
+interface ControlsAndContent
+{
+ /**
+ * @return Controls
+ */
+ public function controls();
+
+ /**
+ * @return Tabs
+ */
+ public function tabs();
+
+ /**
+ * @param HtmlDocument|null $actionBar
+ * @return HtmlDocument
+ */
+ public function actions(HtmlDocument $actionBar = null);
+
+ /**
+ * @return Content
+ */
+ public function content();
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function setTitle($title);
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function addTitle($title);
+
+ /**
+ * @param $title
+ * @param null $url
+ * @param string $name
+ * @return $this
+ */
+ public function addSingleTab($title, $url = null, $name = 'main');
+
+ /**
+ * @return Url
+ */
+ public function url();
+
+ /**
+ * @return Url
+ */
+ public function getOriginalUrl();
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ListItem.php b/vendor/gipfl/icingaweb2/src/Widget/ListItem.php
new file mode 100644
index 0000000..fa4b562
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ListItem.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\ValidHtml;
+
+class ListItem extends BaseHtmlElement
+{
+ protected $contentSeparator = "\n";
+
+ /**
+ * @param ValidHtml|array|string $content
+ * @param Attributes|array $attributes
+ *
+ * @return $this
+ */
+ public function addItem($content, $attributes = null)
+ {
+ return $this->add(
+ Html::tag('li', $attributes, $content)
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php b/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php
new file mode 100644
index 0000000..971a833
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\Table;
+
+class NameValueTable extends Table
+{
+ protected $defaultAttributes = ['class' => 'name-value-table'];
+
+ public function createNameValueRow($name, $value)
+ {
+ return $this::tr([$this::th($name), $this::td($value)]);
+ }
+
+ public function addNameValueRow($name, $value)
+ {
+ return $this->add($this->createNameValueRow($name, $value));
+ }
+
+ public function addNameValuePairs($pairs)
+ {
+ foreach ($pairs as $name => $value) {
+ $this->addNameValueRow($name, $value);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Paginator.php b/vendor/gipfl/icingaweb2/src/Widget/Paginator.php
new file mode 100644
index 0000000..3c255a7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Paginator.php
@@ -0,0 +1,463 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use Icinga\Exception\ProgrammingError;
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\IcingaWeb2\Icon;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+class Paginator extends BaseHtmlElement
+{
+ use TranslationHelper;
+
+ protected $tag = 'div';
+
+ protected $defaultAttributes = [
+ 'class' => 'pagination-control',
+ 'role' => 'navigation',
+ ];
+
+ /** @var Paginatable The query the paginator widget is created for */
+ protected $query;
+
+ /** @var int */
+ protected $pageCount;
+
+ /** @var int */
+ protected $currentCount;
+
+ /** @var Url */
+ protected $url;
+
+ /** @var string */
+ protected $pageParam;
+
+ /** @var string */
+ protected $perPageParam;
+
+ /** @var int */
+ protected $totalCount;
+
+ /** @var int */
+ protected $defaultItemCountPerPage = 25;
+
+ public function __construct(
+ Paginatable $query,
+ Url $url,
+ $pageParameter = 'page',
+ $perPageParameter = 'limit'
+ ) {
+ $this->query = $query;
+ $this->setPageParam($pageParameter);
+ $this->setPerPageParam($perPageParameter);
+ $this->setUrl($url);
+ }
+
+ public function setItemsPerPage($count)
+ {
+ // TODO: this should become setOffset once available
+ $query = $this->getQuery();
+ $query->setLimit($count);
+
+ return $this;
+ }
+
+ protected function setPageParam($pageParam)
+ {
+ $this->pageParam = $pageParam;
+ return $this;
+ }
+
+ protected function setPerPageParam($perPageParam)
+ {
+ $this->perPageParam = $perPageParam;
+ return $this;
+ }
+
+ public function getPageParam()
+ {
+ return $this->pageParam;
+ }
+
+ public function getPerPageParam()
+ {
+ return $this->perPageParam;
+ }
+
+ public function getCurrentPage()
+ {
+ $query = $this->getQuery();
+ if ($query->hasOffset()) {
+ return ($query->getOffset() / $this->getItemsPerPage()) + 1;
+ } else {
+ return 1;
+ }
+ }
+
+ protected function setCurrentPage($page)
+ {
+ $page = (int) $page;
+ $offset = $this->firstRowOnPage($page) - 1;
+ if ($page > 1) {
+ $query = $this->getQuery();
+ $query->setOffset($offset);
+ }
+ }
+
+ public function getPageCount()
+ {
+ if ($this->pageCount === null) {
+ $this->pageCount = (int) ceil($this->getTotalItemCount() / $this->getItemsPerPage());
+ }
+
+ return $this->pageCount;
+ }
+
+ protected function getItemsPerPage()
+ {
+ $limit = $this->getQuery()->getLimit();
+ if ($limit === null) {
+ throw new ProgrammingError('Something went wrong, got no limit when there should be one');
+ } else {
+ return $limit;
+ }
+ }
+
+ public function getTotalItemCount()
+ {
+ if ($this->totalCount === null) {
+ $this->totalCount = count($this->getQuery());
+ }
+
+ return $this->totalCount;
+ }
+
+ public function getPrevious()
+ {
+ if ($this->hasPrevious()) {
+ return $this->getCurrentPage() - 1;
+ } else {
+ return null;
+ }
+ }
+
+ public function hasPrevious()
+ {
+ return $this->getCurrentPage() > 1;
+ }
+
+ public function getNext()
+ {
+ if ($this->hasNext()) {
+ return $this->getCurrentPage() + 1;
+ } else {
+ return null;
+ }
+ }
+
+ public function hasNext()
+ {
+ return $this->getCurrentPage() < $this->getPageCount();
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Returns an array of "local" pages given the page count and current page number
+ *
+ * @return array
+ */
+ protected function getPages()
+ {
+ $page = $this->getPageCount();
+ $current = $this->getCurrentPage();
+
+ $range = [];
+
+ if ($page < 10) {
+ // Show all pages if we have less than 10
+ for ($i = 1; $i < 10; $i++) {
+ if ($i > $page) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ } else {
+ // More than 10 pages:
+ foreach ([1, 2] as $i) {
+ $range[$i] = $i;
+ }
+
+ if ($current < 6) {
+ // We are on page 1-5 from
+ for ($i = 1; $i <= 7; $i++) {
+ $range[$i] = $i;
+ }
+ } else {
+ // Current page > 5
+ $range[] = '…';
+
+ if (($page - $current) < 5) {
+ // Less than 5 pages left
+ $start = 5 - ($page - $current);
+ } else {
+ $start = 1;
+ }
+
+ for ($i = $current - $start; $i < ($current + (4 - $start)); $i++) {
+ if ($i > $page) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ }
+
+ if ($current < ($page - 2)) {
+ $range[] = '…';
+ }
+
+ foreach ([$page - 1, $page] as $i) {
+ $range[$i] = $i;
+ }
+ }
+
+ if (empty($range)) {
+ $range[] = 1;
+ }
+
+ return $range;
+ }
+
+ public function getDefaultItemCountPerPage()
+ {
+ return $this->defaultItemCountPerPage;
+ }
+
+ public function setDefaultItemCountPerPage($count)
+ {
+ $this->defaultItemCountPerPage = (int) $count;
+ return $this;
+ }
+
+ public function setUrl(Url $url)
+ {
+ $page = (int) $url->shift($this->getPageParam());
+ $perPage = (int) $url->getParam($this->getPerPageParam());
+ if ($perPage > 0) {
+ $this->setItemsPerPage($perPage);
+ } else {
+ if (! $this->getQuery()->hasLimit()) {
+ $this->setItemsPerPage($this->getDefaultItemCountPerPage());
+ }
+ }
+ if ($page > 0) {
+ $this->setCurrentPage($page);
+ }
+
+ $this->url = $url;
+
+ return $this;
+ }
+
+ public function getUrl()
+ {
+ if ($this->url === null) {
+ $this->setUrl(Url::fromRequest());
+ }
+
+ return $this->url;
+ }
+
+ public function getPreviousLabel()
+ {
+ return $this->getLabel($this->getCurrentPage() - 1);
+ }
+
+ protected function getNextLabel()
+ {
+ return $this->getLabel($this->getCurrentPage() + 1);
+ }
+
+ protected function getLabel($page)
+ {
+ return sprintf(
+ $this->translate('Show rows %u to %u of %u'),
+ $this->firstRowOnPage($page),
+ $this->lastRowOnPage($page),
+ $this->getTotalItemCount()
+ );
+ }
+
+ protected function renderPrevious()
+ {
+ return Html::tag('li', [
+ 'class' => 'nav-item'
+ ], Link::create(
+ Icon::create('angle-double-left'),
+ $this->makeUrl($this->getPrevious()),
+ null,
+ [
+ 'title' => $this->getPreviousLabel(),
+ 'class' => 'previous-page'
+ ]
+ ));
+ }
+
+ protected function renderNoPrevious()
+ {
+ return $this->renderDisabled(Html::tag('span', [
+ 'class' => 'previous-page'
+ ], [
+ $this->srOnly($this->translate('Previous page')),
+ Icon::create('angle-double-left')
+ ]));
+ }
+
+ protected function renderNext()
+ {
+ return Html::tag('li', [
+ 'class' => 'nav-item'
+ ], Link::create(
+ Icon::create('angle-double-right'),
+ $this->makeUrl($this->getNext()),
+ null,
+ [
+ 'title' => $this->getNextLabel(),
+ 'class' => 'next-page'
+ ]
+ ));
+ }
+
+ protected function renderNoNext()
+ {
+ return $this->renderDisabled(Html::tag('span', [
+ 'class' => 'previous-page'
+ ], [
+ $this->srOnly($this->translate('Next page')),
+ Icon::create('angle-double-right')
+ ]));
+ }
+
+ protected function renderDots()
+ {
+ return $this->renderDisabled(Html::tag('span', null, '…'));
+ }
+
+ protected function renderInnerPages()
+ {
+ $pages = [];
+ $current = $this->getCurrentPage();
+
+ foreach ($this->getPages() as $page) {
+ if ($page === '…') {
+ $pages[] = $this->renderDots();
+ } else {
+ $pages[] = Html::tag(
+ 'li',
+ $page === $current ? ['class' => 'active'] : null,
+ $this->makeLink($page)
+ );
+ }
+ }
+
+ return $pages;
+ }
+
+ protected function lastRowOnPage($page)
+ {
+ $perPage = $this->getItemsPerPage();
+ $total = $this->getTotalItemCount();
+ $last = $page * $perPage;
+ if ($last > $total) {
+ $last = $total;
+ }
+
+ return $last;
+ }
+
+ protected function firstRowOnPage($page)
+ {
+ return ($page - 1) * $this->getItemsPerPage() + 1;
+ }
+
+ protected function makeLink($page)
+ {
+ return Link::create(
+ $page,
+ $this->makeUrl($page),
+ null,
+ ['title' => $this->getLabel($page)]
+ );
+ }
+
+ protected function makeUrl($page)
+ {
+ if ($page) {
+ return $this->getUrl()->with('page', $page);
+ } else {
+ return $this->getUrl();
+ }
+ }
+
+ protected function srOnly($content)
+ {
+ return Html::tag('span', ['class' => 'sr-only'], $content);
+ }
+
+ protected function renderDisabled($content)
+ {
+ return Html::tag('li', [
+ 'class' => ['nav-item', 'disabled'],
+ 'aria-hidden' => 'true'
+ ], $content);
+ }
+
+ protected function renderList()
+ {
+ return Html::tag(
+ 'ul',
+ ['class' => ['nav', 'tab-nav']],
+ [
+ $this->hasPrevious() ? $this->renderPrevious() : $this->renderNoPrevious(),
+ $this->renderInnerPages(),
+ $this->hasNext() ? $this->renderNext() : $this->renderNoNext()
+ ]
+ );
+ }
+
+ public function assemble()
+ {
+ $this->add([
+ $this->renderScreenReaderHeader(),
+ $this->renderList()
+ ]);
+ }
+
+ protected function renderScreenReaderHeader()
+ {
+ return Html::tag('h2', [
+ // 'id' => $this->protectId('pagination') -> why?
+ 'class' => 'sr-only',
+ 'tab-index' => '-1'
+ ], $this->translate('Pagination'));
+ }
+
+ public function render()
+ {
+ if ($this->getPageCount() < 2) {
+ return '';
+ } else {
+ return parent::render();
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Tabs.php b/vendor/gipfl/icingaweb2/src/Widget/Tabs.php
new file mode 100644
index 0000000..38bf4cd
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Tabs.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use Exception;
+use Icinga\Web\Widget\Tabs as WebTabs;
+use InvalidArgumentException;
+use ipl\Html\ValidHtml;
+
+class Tabs extends WebTabs implements ValidHtml
+{
+ /**
+ * @param string $name
+ * @return $this
+ */
+ public function activate($name)
+ {
+ try {
+ parent::activate($name);
+ } catch (Exception $e) {
+ throw new InvalidArgumentException(
+ "Can't activate '$name', there is no such tab"
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param array|\Icinga\Web\Widget\Tab $tab
+ * @return $this
+ */
+ public function add($name, $tab)
+ {
+ try {
+ parent::add($name, $tab);
+ } catch (Exception $e) {
+ throw new InvalidArgumentException($e->getMessage());
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php
new file mode 100644
index 0000000..07204b8
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\ZfDb\Select;
+use RuntimeException;
+use Zend_Db_Select as ZfSelect;
+
+class CountQuery
+{
+ /** @var Select|ZfSelect */
+ private $query;
+
+ private $maxRows;
+
+ /**
+ * ZfCountQuery constructor.
+ * @param Select|ZfSelect $query
+ */
+ public function __construct($query)
+ {
+ if ($query instanceof Select || $query instanceof ZfSelect) {
+ $this->query = $query;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+ }
+
+ public function setMaxRows($max)
+ {
+ $this->maxRows = $max;
+ return $this;
+ }
+
+ public function getQuery()
+ {
+ if ($this->needsSubQuery()) {
+ return $this->buildSubQuery();
+ } else {
+ return $this->buildSimpleQuery();
+ }
+ }
+
+ protected function hasOneOf($parts)
+ {
+ foreach ($parts as $part) {
+ if ($this->hasPart($part)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function hasPart($part)
+ {
+ $values = $this->query->getPart($part);
+ return ! empty($values);
+ }
+
+ protected function needsSubQuery()
+ {
+ return null !== $this->maxRows || $this->hasOneOf([
+ Select::GROUP,
+ Select::UNION
+ ]);
+ }
+
+ protected function buildSubQuery()
+ {
+ $sub = clone($this->query);
+ $sub->limit(null, null);
+ $class = $this->query;
+ $query = new $class($this->query->getAdapter());
+ $query->from($sub, ['cnt' => 'COUNT(*)']);
+ if (null !== $this->maxRows) {
+ $sub->limit($this->maxRows + 1);
+ }
+
+ return $query;
+ }
+
+ protected function buildSimpleQuery()
+ {
+ $query = clone($this->query);
+ $query->reset(Select::COLUMNS);
+ $query->reset(Select::ORDER);
+ $query->reset(Select::LIMIT_COUNT);
+ $query->reset(Select::LIMIT_OFFSET);
+ $query->columns(['cnt' => 'COUNT(*)']);
+ return $query;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php
new file mode 100644
index 0000000..b51296f
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php
@@ -0,0 +1,335 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\ZfDb\Adapter\Adapter as Db;
+use gipfl\ZfDb\Exception\SelectException;
+use gipfl\ZfDb\Expr;
+use gipfl\ZfDb\Select;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterAnd;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Filter\FilterNot;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use InvalidArgumentException;
+use RuntimeException;
+use Zend_Db_Adapter_Abstract as DbAdapter;
+use Zend_Db_Expr as DbExpr;
+use Zend_Db_Select as DbSelect;
+use Zend_Db_Select_Exception as DbSelectException;
+
+class FilterRenderer
+{
+ private $db;
+
+ /** @var Filter */
+ private $filter;
+
+ /** @var array */
+ private $columnMap;
+
+ /** @var string */
+ private $dbExprClass;
+
+ /**
+ * FilterRenderer constructor.
+ * @param Filter $filter
+ * @param Db|DbAdapter $db
+ */
+ public function __construct(Filter $filter, $db)
+ {
+ $this->filter = $filter;
+ if ($db instanceof Db) {
+ $this->db = $db;
+ $this->dbExprClass = Expr::class;
+ } elseif ($db instanceof DbAdapter) {
+ $this->db = $db;
+ $this->dbExprClass = DbExpr::class;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 DB adapter');
+ }
+ }
+
+ /**
+ * @return Expr|DbExpr
+ */
+ public function toDbExpression()
+ {
+ return $this->expr($this->render());
+ }
+
+ /**
+ * @return Expr|DbExpr
+ */
+ protected function expr($content)
+ {
+ $class = $this->dbExprClass;
+ return new $class($content);
+ }
+
+ /**
+ * @param Filter $filter
+ * @param Select|DbSelect|SimpleQuery $query
+ * @return Select|DbSelect|SimpleQuery
+ */
+ public static function applyToQuery(Filter $filter, $query)
+ {
+ if ($query instanceof SimpleQuery) {
+ $query->applyFilter($filter);
+ return $query;
+ }
+ if (! ($query instanceof Select || $query instanceof DbSelect)) {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+
+ if (! $filter->isEmpty()) {
+ $renderer = new static($filter, $query->getAdapter());
+ $renderer->extractColumnMap($query);
+ $query->where($renderer->toDbExpression());
+ }
+
+ return $query;
+ }
+
+ protected function lookupColumnAlias($column)
+ {
+ if (array_key_exists($column, $this->columnMap)) {
+ return $this->columnMap[$column];
+ } else {
+ return $column;
+ }
+ }
+
+ protected function extractColumnMap($query)
+ {
+ $map = [];
+ try {
+ $columns = $query->getPart(Select::COLUMNS);
+ } catch (SelectException $e) {
+ // Will not happen.
+ throw new RuntimeException($e->getMessage());
+ } catch (DbSelectException $e) {
+ // Will not happen.
+ throw new RuntimeException($e->getMessage());
+ }
+
+ foreach ($columns as $col) {
+ if ($col[1] instanceof Expr || $col[1] instanceof DbExpr) {
+ $map[$col[2]] = (string) $col[1];
+ $map[$col[2]] = $col[1];
+ } else {
+ $map[$col[2]] = $col[0] . '.' . $col[1];
+ }
+ }
+
+ $this->columnMap = $map;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return $this->renderFilter($this->filter);
+ }
+
+ protected function renderFilterChain(FilterChain $filter, $level = 0)
+ {
+ $prefix = '';
+
+ if ($filter instanceof FilterAnd) {
+ $op = ' AND ';
+ } elseif ($filter instanceof FilterOr) {
+ $op = ' OR ';
+ } elseif ($filter instanceof FilterNot) {
+ $op = ' AND ';
+ $prefix = 'NOT ';
+ } else {
+ throw new InvalidArgumentException(
+ 'Cannot render a %s filter chain for Zf Db',
+ get_class($filter)
+ );
+ }
+
+ $parts = [];
+ if ($filter->isEmpty()) {
+ // Hint: we might want to fail here
+ return '';
+ } else {
+ foreach ($filter->filters() as $f) {
+ $part = $this->renderFilter($f, $level + 1);
+ if ($part !== '') {
+ $parts[] = $part;
+ }
+ }
+ if (empty($parts)) {
+ // will not happen, as we are not empty
+ return '';
+ } else {
+ if ($level > 0) {
+ return "$prefix (" . implode($op, $parts) . ')';
+ } else {
+ return $prefix . implode($op, $parts);
+ }
+ }
+ }
+ }
+
+ protected function renderFilterExpression(FilterExpression $filter)
+ {
+ $col = $this->lookupColumnAlias($filter->getColumn());
+ if (! $col instanceof Expr && ! $col instanceof DbExpr && ! ctype_digit($col)) {
+ $col = $this->db->quoteIdentifier($col);
+ }
+ $sign = $filter->getSign();
+ $expression = $filter->getExpression();
+
+ if (is_array($expression)) {
+ return $this->renderArrayExpression($col, $sign, $expression);
+ }
+
+ if ($sign === '=') {
+ if (strpos($expression, '*') === false) {
+ return $this->renderAny($col, $sign, $expression);
+ } else {
+ return $this->renderLike($col, $expression);
+ }
+ }
+
+ if ($sign === '!=') {
+ if (strpos($expression, '*') === false) {
+ return $this->renderAny($col, $sign, $expression);
+ } else {
+ return $this->renderNotLike($col, $expression);
+ }
+ }
+
+ return $this->renderAny($col, $sign, $expression);
+ }
+
+
+ protected function renderLike($col, $expression)
+ {
+ if ($expression === '*') {
+ return $this->expr('TRUE');
+ }
+
+ return $col . ' LIKE ' . $this->escape($this->escapeWildcards($expression));
+ }
+
+ protected function renderNotLike($col, $expression)
+ {
+ if ($expression === '*') {
+ return $this->expr('FALSE');
+ }
+
+ return sprintf(
+ '(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
+ $col,
+ $this->escape($this->escapeWildcards($expression))
+ );
+ }
+
+ protected function renderNotEqual($col, $expression)
+ {
+ return sprintf('(%1$s != %2$s OR %1$s IS NULL)', $col, $this->escape($expression));
+ }
+
+ protected function renderAny($col, $sign, $expression)
+ {
+ return sprintf('%s %s %s', $col, $sign, $this->escape($expression));
+ }
+
+ protected function renderArrayExpression($col, $sign, $expression)
+ {
+ if ($sign === '=') {
+ return $col . ' IN (' . $this->escape($expression) . ')';
+ } elseif ($sign === '!=') {
+ return sprintf(
+ '(%1$s NOT IN (%2$s) OR %1$s IS NULL)',
+ $col,
+ $this->escape($expression)
+ );
+ }
+
+ throw new InvalidArgumentException(
+ 'Array expressions can only be rendered for = and !=, got %s',
+ $sign
+ );
+ }
+
+ /**
+ * @param Filter $filter
+ * @param int $level
+ * @return string|Expr|DbExpr
+ */
+ protected function renderFilter(Filter $filter, $level = 0)
+ {
+ if ($filter instanceof FilterChain) {
+ return $this->renderFilterChain($filter, $level);
+ } elseif ($filter instanceof FilterExpression) {
+ return $this->renderFilterExpression($filter);
+ } else {
+ throw new RuntimeException(sprintf(
+ 'Filter of type FilterChain or FilterExpression expected, got %s',
+ get_class($filter)
+ ));
+ }
+ }
+
+ protected function escape($value)
+ {
+ // bindParam? bindValue?
+ if (is_array($value)) {
+ $ret = [];
+ foreach ($value as $val) {
+ $ret[] = $this->escape($val);
+ }
+ return implode(', ', $ret);
+ } else {
+ return $this->db->quote($value);
+ }
+ }
+
+ protected function escapeWildcards($value)
+ {
+ return preg_replace('/\*/', '%', $value);
+ }
+
+ public function whereToSql($col, $sign, $expression)
+ {
+ if (is_array($expression)) {
+ if ($sign === '=') {
+ return $col . ' IN (' . $this->escape($expression) . ')';
+ } elseif ($sign === '!=') {
+ return sprintf('(%1$s NOT IN (%2$s) OR %1$s IS NULL)', $col, $this->escape($expression));
+ }
+
+ throw new InvalidArgumentException(
+ 'Unable to render array expressions with operators other than equal or not equal'
+ );
+ } elseif ($sign === '=' && strpos($expression, '*') !== false) {
+ if ($expression === '*') {
+ return $this->expr('TRUE');
+ }
+
+ return $col . ' LIKE ' . $this->escape($this->escapeWildcards($expression));
+ } elseif ($sign === '!=' && strpos($expression, '*') !== false) {
+ if ($expression === '*') {
+ return $this->expr('FALSE');
+ }
+
+ return sprintf(
+ '(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
+ $col,
+ $this->escape($this->escapeWildcards($expression))
+ );
+ } elseif ($sign === '!=') {
+ return sprintf('(%1$s %2$s %3$s OR %1$s IS NULL)', $col, $sign, $this->escape($expression));
+ } else {
+ return sprintf('%s %s %s', $col, $sign, $this->escape($expression));
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php
new file mode 100644
index 0000000..599a3ee
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\ZfDb\Select;
+use gipfl\ZfDb\Exception\SelectException;
+use Icinga\Application\Benchmark;
+use RuntimeException;
+use Zend_Db_Select as ZfSelect;
+use Zend_Db_Select_Exception as ZfDbSelectException;
+
+class SelectPaginationAdapter implements Paginatable
+{
+ private $query;
+
+ private $countQuery;
+
+ private $cachedCount;
+
+ private $cachedCountQuery;
+
+ public function __construct($query)
+ {
+ if ($query instanceof Select || $query instanceof ZfSelect) {
+ $this->query = $query;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+ }
+
+ public function getCountQuery()
+ {
+ if ($this->countQuery === null) {
+ $this->countQuery = (new CountQuery($this->query))->getQuery();
+ }
+
+ return $this->countQuery;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ $queryString = (string) $this->getCountQuery();
+ if ($this->cachedCountQuery !== $queryString) {
+ Benchmark::measure('Running count() for pagination');
+ $this->cachedCountQuery = $queryString;
+ $count = $this->query->getAdapter()->fetchOne(
+ $queryString
+ );
+ $this->cachedCount = $count;
+ Benchmark::measure("Counted $count rows");
+ }
+
+ return $this->cachedCount;
+ }
+
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ }
+
+ public function hasLimit()
+ {
+ return $this->getLimit() !== null;
+ }
+
+ public function getLimit()
+ {
+ return $this->getQueryPart(Select::LIMIT_COUNT);
+ }
+
+ public function setLimit($limit)
+ {
+ $this->query->limit(
+ $limit,
+ $this->getOffset()
+ );
+ }
+
+ public function hasOffset()
+ {
+ return $this->getOffset() !== null;
+ }
+
+ public function getOffset()
+ {
+ return $this->getQueryPart(Select::LIMIT_OFFSET);
+ }
+
+ protected function getQueryPart($part)
+ {
+ try {
+ return $this->query->getPart($part);
+ } catch (SelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ } catch (ZfDbSelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ }
+ }
+
+ public function setOffset($offset)
+ {
+ $this->query->limit(
+ $this->getLimit(),
+ $offset
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php b/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php
new file mode 100644
index 0000000..89b36a4
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1;
+
+use gipfl\IcingaWeb2\Widget\Content;
+use gipfl\IcingaWeb2\Widget\Controls;
+use ipl\Html\Error;
+use Icinga\Application\Icinga;
+use ipl\Html\ValidHtml;
+use Zend_Controller_Action_Helper_Abstract as Helper;
+use Zend_Controller_Action_HelperBroker as HelperBroker;
+
+class SimpleViewRenderer extends Helper implements ValidHtml
+{
+ private $disabled = false;
+
+ private $rendered = false;
+
+ /** @var \Zend_View_Interface */
+ public $view;
+
+ public function init()
+ {
+ // Register view with action controller (unless already registered)
+ if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
+ $this->_actionController->view = $this->view;
+ }
+ }
+
+ public function disable($disabled = true)
+ {
+ $this->disabled = $disabled;
+ return $this;
+ }
+
+ public function replaceZendViewRenderer()
+ {
+ /** @var \Zend_Controller_Action_Helper_ViewRenderer $viewRenderer */
+ $viewRenderer = Icinga::app()->getViewRenderer();
+ $viewRenderer->setNeverRender();
+ $viewRenderer->setNeverController();
+ HelperBroker::removeHelper('viewRenderer');
+ HelperBroker::addHelper($this);
+ $this->view = $viewRenderer->view;
+ return $this;
+ }
+
+ public function render($action = null, $name = null, $noController = null)
+ {
+ if (null === $name) {
+ $name = null; // $this->getResponseSegment();
+ }
+ // Compat.
+ if (isset($this->_actionController)
+ && get_class($this->_actionController) === 'Icinga\\Controllers\\ErrorController'
+ ) {
+ $html = $this->simulateErrorController();
+ } else {
+ $html = '';
+ if (null !== $this->view->controls) {
+ $html .= $this->view->controls->__toString();
+ }
+
+ if (null !== $this->view->content) {
+ $html .= $this->view->content->__toString();
+ }
+ }
+
+ $this->getResponse()->appendBody($html, $name);
+ // $this->setNoRender();
+ $this->rendered = true;
+ }
+
+ protected function simulateErrorController()
+ {
+ $errorHandler = $this->_actionController->getParam('error_handler');
+ if (isset($errorHandler->exception)) {
+ $error = Error::show($errorHandler->exception);
+ } else {
+ $error = 'An unknown error occured';
+ }
+
+ /** @var \Icinga\Web\Request $request */
+ $request = $this->getRequest();
+ $controls = new Controls();
+ $controls->getTabs()->add('error', [
+ 'label' => t('Error'),
+ 'url' => $request->getUrl(),
+ ])->activate('error');
+ $content = new Content();
+ $content->add($error);
+
+ return $controls . $content;
+ }
+
+ public function shouldRender()
+ {
+ return ! $this->disabled && ! $this->rendered;
+ }
+
+ public function postDispatch()
+ {
+ if ($this->shouldRender()) {
+ $this->render();
+ }
+ }
+
+ public function getName()
+ {
+ // TODO: This is wrong, should be 'viewRenderer' - but that would
+ // currently break nearly everything, starting with full layout
+ // rendering
+ return 'ViewRenderer';
+ }
+}