diff options
Diffstat (limited to '')
-rw-r--r-- | library/Cube/Ido/CustomVarDimension.php | 146 | ||||
-rw-r--r-- | library/Cube/Ido/DataView/Hoststatus.php | 17 | ||||
-rw-r--r-- | library/Cube/Ido/DbCube.php | 298 | ||||
-rw-r--r-- | library/Cube/Ido/IdoCube.php | 219 | ||||
-rw-r--r-- | library/Cube/Ido/IdoHostStatusCube.php | 97 | ||||
-rw-r--r-- | library/Cube/Ido/IdoServiceStatusCube.php | 97 | ||||
-rw-r--r-- | library/Cube/Ido/Query/HoststatusQuery.php | 47 | ||||
-rw-r--r-- | library/Cube/Ido/ZfSelectWrapper.php | 77 |
8 files changed, 998 insertions, 0 deletions
diff --git a/library/Cube/Ido/CustomVarDimension.php b/library/Cube/Ido/CustomVarDimension.php new file mode 100644 index 0000000..df45497 --- /dev/null +++ b/library/Cube/Ido/CustomVarDimension.php @@ -0,0 +1,146 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido; + +use Icinga\Module\Cube\Cube; +use Icinga\Module\Cube\Dimension; + +/** + * CustomVarDimension + * + * This provides dimenstions for custom variables available in the IDO + * + * TODO: create safe aliases for special characters + * + * @package Icinga\Module\Cube\Ido + */ +class CustomVarDimension implements Dimension +{ + public const TYPE_HOST = 'host'; + + public const TYPE_SERVICE = 'service'; + + /** + * @var string custom variable name + */ + protected $varName; + + /** + * @var string custom variable label + */ + protected $varLabel; + + /** + * @var bool Whether null values should be shown + */ + protected $wantNull = false; + + /** @var string Type of the custom var */ + protected $type; + + /** + * CustomVarDimension constructor. + * + * @param $varName + * @param string $type Type of the custom var + */ + public function __construct($varName, $type = null) + { + $this->varName = $varName; + $this->type = $type; + } + + /** + * Define whether null values should be shown + * + * @param bool $wantNull + * @return $this + */ + public function wantNull($wantNull = true) + { + $this->wantNull = $wantNull; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->varName; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->varLabel ?: $this->getName(); + } + + public function setLabel($label) + { + $this->varLabel = $label; + + return $this; + } + + public function addLabel($label) + { + if ($this->varLabel === null) { + $this->setLabel($label); + } else { + $this->varLabel .= ' & ' . $label; + } + + return $this; + } + + public function getColumnExpression(Cube $cube) + { + /** @var IdoCube $cube */ + if ($this->wantNull) { + return 'COALESCE(' . $cube->db()->quoteIdentifier(['c_' . $this->varName, 'varvalue']) . ", '-')"; + } else { + return $cube->db()->quoteIdentifier(['c_' . $this->varName, 'varvalue']); + } + } + + protected function safeVarname($name) + { + return $name; + } + + public function addToCube(Cube $cube) + { + switch ($this->type) { + case self::TYPE_HOST: + $objectId = 'ho.object_id'; + break; + case self::TYPE_SERVICE: + $objectId = 'so.object_id'; + break; + default: + $objectId = 'o.object_id'; + } + $name = $this->safeVarname($this->varName); + /** @var IdoCube $cube */ + $alias = $cube->db()->quoteIdentifier(['c_' . $name]); + + if ($cube->isPgsql()) { + $on = "LOWER($alias.varname) = ?"; + $name = strtolower($name); + } else { + $on = $alias . '.varname = ? COLLATE latin1_general_ci'; + } + + $cube->innerQuery()->joinLeft( + array($alias => $cube->tableName('icinga_customvariablestatus')), + $cube->db()->quoteInto($on, $name) + . ' AND ' . $alias . '.object_id = ' . $objectId, + array() + )->group($alias . '.varvalue'); + } +} diff --git a/library/Cube/Ido/DataView/Hoststatus.php b/library/Cube/Ido/DataView/Hoststatus.php new file mode 100644 index 0000000..32ee44b --- /dev/null +++ b/library/Cube/Ido/DataView/Hoststatus.php @@ -0,0 +1,17 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2021 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido\DataView; + +use Icinga\Data\ConnectionInterface; +use Icinga\Module\Cube\Ido\Query\HoststatusQuery; + +class Hoststatus extends \Icinga\Module\Monitoring\DataView\Hoststatus +{ + public function __construct(ConnectionInterface $connection, array $columns = null) + { + $this->connection = $connection; + $this->query = new HoststatusQuery($connection->getResource(), $columns); + } +} diff --git a/library/Cube/Ido/DbCube.php b/library/Cube/Ido/DbCube.php new file mode 100644 index 0000000..5fc5b47 --- /dev/null +++ b/library/Cube/Ido/DbCube.php @@ -0,0 +1,298 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido; + +use Icinga\Data\Db\DbConnection; +use Icinga\Module\Cube\Cube; + +abstract class DbCube extends Cube +{ + /** @var DbConnection */ + protected $connection; + + /** @var \Zend_Db_Adapter_Abstract */ + protected $db; + + /** @var ZfSelectWrapper The inner query fetching all required data */ + protected $innerQuery; + + /** @var \Zend_Db_Select The rollup query, creating grouped sums over innerQuery */ + protected $rollupQuery; + + /** @var \Zend_Db_Select The outer query, orders respecting NULL values, rollup first */ + protected $fullQuery; + + /** @var string Database name. Allows to eventually join over multiple dbs */ + protected $dbName; + + /** @var array Key/value array containing our chosen facts and the corresponding SQL expression */ + protected $factColumns = array(); + + /** + * A DbCube must provide a list of all available columns + * + * This is a key/value array with the key being the fact name / column alias + * and + * + * @return array + */ + abstract public function getAvailableFactColumns(); + + /** + * @return \Zend_Db_Select + */ + abstract public function prepareInnerQuery(); + + /** + * Set a database connection + * + * @param DbConnection $connection + * @return $this + */ + public function setConnection(DbConnection $connection) + { + $this->connection = $connection; + $this->db = $connection->getDbAdapter(); + return $this; + } + + /** + * Prepare the query and fetch all data + * + * @return array + */ + public function fetchAll() + { + $query = $this->fullQuery(); + return $this->db()->fetchAll($query); + } + + /** + * Choose a one or more facts + * + * This also initializes a fact column lookup array + * + * @param array $facts + * @return $this + */ + public function chooseFacts(array $facts) + { + parent::chooseFacts($facts); + + $this->factColumns = array(); + $columns = $this->getAvailableFactColumns(); + foreach ($this->chosenFacts as $name) { + $this->factColumns[$name] = $columns[$name]; + } + + return $this; + } + + /** + * @param $name + * @return $this + */ + public function setDbName($name) + { + $this->dbName = $name; + return $this; + } + + /** + * Gives back the table name, eventually prefixed with a defined DB name + * + * @param string $name + * @return string + */ + public function tableName($name) + { + if ($this->dbName === null) { + return $name; + } else { + return $this->dbName . '.' . $name; + } + } + + /** + * Returns an eventually defined DB name + * + * @return string|null + */ + public function getDbName() + { + return $this->dbName; + } + + /** + * Get our inner query + * + * Hint: mostly used to get rid of NULL values + * + * @return ZfSelectWrapper + */ + public function innerQuery() + { + if ($this->innerQuery === null) { + $this->innerQuery = new ZfSelectWrapper($this->prepareInnerQuery()); + } + + return $this->innerQuery; + } + + /** + * We first prepare the queries and to finalize it later on + * + * This way dimensions can be added one by one, they will be allowed to + * optionally join additional tables or apply other modifications late + * in the process + * + * @return void + */ + public function finalizeInnerQuery() + { + $query = $this->innerQuery()->unwrap(); + $columns = array(); + foreach ($this->dimensions as $name => $dimension) { + $dimension->addToCube($this); + if ($this->hasSlice($name)) { + $query->where( + $dimension->getColumnExpression($this) . ' = ?', + $this->slices[$name] + ); + } else { + $columns[$name] = $dimension->getColumnExpression($this); + } + } + + $c = []; + + foreach ($columns + $this->factColumns as $k => $v) { + $c[$this->db()->quoteIdentifier([$k])] = $v; + } + + $query->columns($c); + } + + /** + * Lazy-load our full query + * + * @return \Zend_Db_Select + */ + protected function fullQuery() + { + if ($this->fullQuery === null) { + $this->fullQuery = $this->prepareFullQuery(); + } + + return $this->fullQuery; + } + + /** + * Lazy-load our full query + * + * @return \Zend_Db_Select + */ + protected function rollupQuery() + { + if ($this->rollupQuery === null) { + $this->rollupQuery = $this->prepareRollupQuery(); + } + + return $this->rollupQuery; + } + + /** + * The full query wraps the rollup query in a sub-query to work around + * MySQL limitations. This is required to not get into trouble when ordering, + * especially combined with the need to keep control over (eventually desired) + * NULL value fact columns + * + * @return \Zend_Db_Select + */ + protected function prepareFullQuery() + { + $alias = 'rollup'; + $cols = $this->listColumns(); + + $columns = array(); + + foreach ($cols as $col) { + $columns[$this->db()->quoteIdentifier([$col])] = $alias . '.' . $this->db()->quoteIdentifier([$col]); + } + + $select = $this->db()->select()->from( + array($alias => $this->rollupQuery()), + $columns + ); + + foreach ($columns as $col) { + $select->order('(' . $col . ' IS NOT NULL)'); + $select->order($col); + } + + return $select; + } + + /** + * Provide access to our DB + * + * @return \Zend_Db_Adapter_Abstract + */ + public function db() + { + return $this->db; + } + + /** + * Whether our connection is PostgreSQL + * + * @return bool + */ + public function isPgsql() + { + return $this->connection->getDbType() === 'pgsql'; + } + + + /** + * This prepares the rollup query + * + * Inner query is wrapped in a subquery, summaries for all facts are + * fetched. Rollup considers all defined dimensions and expects them + * to exist as columns in the innerQuery + * + * @return \Zend_Db_Select + */ + protected function prepareRollupQuery() + { + $alias = 'sub'; + + $dimensions = array_map(function ($val) { + return $this->db()->quoteIdentifier([$val]); + }, array_keys($this->listDimensions())); + $this->finalizeInnerQuery(); + $columns = array(); + foreach ($dimensions as $dimension) { + $columns[$dimension] = $alias . '.' . $dimension; + } + + foreach ($this->listFacts() as $fact) { + $columns[$fact] = 'SUM(' . $fact . ')'; + } + + $select = $this->db()->select()->from( + array($alias => $this->innerQuery()->unwrap()), + $columns + ); + + if ($this->isPgsql()) { + $select->group('ROLLUP (' . implode(', ', $dimensions) . ')'); + } else { + $select->group('(' . implode('), (', $dimensions) . ') WITH ROLLUP'); + } + + return $select; + } +} diff --git a/library/Cube/Ido/IdoCube.php b/library/Cube/Ido/IdoCube.php new file mode 100644 index 0000000..7ad609d --- /dev/null +++ b/library/Cube/Ido/IdoCube.php @@ -0,0 +1,219 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido; + +use Icinga\Application\Config; +use Icinga\Authentication\Auth; +use Icinga\Data\Filter\Filter; +use Icinga\Exception\ConfigurationError; +use Icinga\Exception\QueryException; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Util\GlobFilter; + +/** + * IdoCube + * + * Base class for IDO-related cubes + * + * @package Icinga\Module\Cube\Ido + */ +abstract class IdoCube extends DbCube +{ + /** @var array */ + protected $availableFacts = array(); + + /** @var string We ask for the IDO version for compatibility reasons */ + protected $idoVersion; + + /** @var MonitoringBackend */ + protected $backend; + + /** + * Cache for {@link filterProtectedCustomvars()} + * + * @var string|null + */ + protected $protectedCustomvars; + + /** @var GlobFilter The properties to hide from the user */ + protected $blacklistedProperties; + + public const IS_USING_ICINGADB = false; + + /** + * Add a specific named dimension + * + * Right now these are just custom vars, we might support group memberships + * or other properties in future + * + * @param string $name + * + * @return $this + */ + public function addDimensionByName($name): self + { + if (count($this->filterProtectedCustomvars([$name])) === 1) { + $this->addDimension($this->createDimension($name)); + } + + return $this; + } + + /** + * We can steal the DB connection directly from a Monitoring backend + * + * @param MonitoringBackend $backend + * @return $this + */ + public function setBackend(MonitoringBackend $backend) + { + $this->backend = $backend; + + $resource = $backend->getResource(); + $resource->getDbAdapter() + ->getConnection() + ->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_NATURAL); + + $this->setConnection($resource); + + return $this; + } + + /** + * Provice access to our DB resource + * + * This lazy-loads the default monitoring backend in case no DB has been + * given + * + * @return \Zend_Db_Adapter_Abstract + */ + public function db() + { + $this->requireBackend(); + return parent::db(); + } + + /** + * Returns the Icinga IDO version + * + * @return string + */ + protected function getIdoVersion() + { + if ($this->idoVersion === null) { + $db = $this->db(); + $this->idoVersion = $db->fetchOne( + $db->select()->from('icinga_dbversion', 'version') + ); + } + + return $this->idoVersion; + } + + /** + * Steal the default monitoring DB resource... + * + * ...in case none has been defined otherwise + * + * @return void + */ + protected function requireBackend() + { + if ($this->db === null) { + $this->setBackend(MonitoringBackend::instance()); + } + } + + protected function getMonitoringRestriction() + { + $restriction = Filter::matchAny(); + $restriction->setAllowedFilterColumns(array( + 'host_name', + 'hostgroup_name', + 'instance_name', + 'service_description', + 'servicegroup_name', + function ($c) { + return preg_match('/^_(?:host|service)_/i', $c); + } + )); + + $filters = Auth::getInstance()->getUser()->getRestrictions('monitoring/filter/objects'); + + foreach ($filters as $filter) { + if ($filter === '*') { + return Filter::matchAny(); + } + try { + $restriction->addFilter(Filter::fromQueryString($filter)); + } catch (QueryException $e) { + throw new ConfigurationError( + 'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s', + 'monitoring/filter/objects', + $filter, + implode(', ', array( + 'instance_name', + 'host_name', + 'hostgroup_name', + 'service_description', + 'servicegroup_name', + '_(host|service)_<customvar-name>' + )), + $e + ); + } + } + + return $restriction; + } + + /** + * Return the given array without values matching the custom variables protected by the monitoring module + * + * @param string[] $customvars + * + * @return string[] + */ + protected function filterProtectedCustomvars(array $customvars) + { + if ($this->blacklistedProperties === null) { + $this->blacklistedProperties = new GlobFilter( + Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') + ); + } + + if ($this instanceof IdoServiceStatusCube) { + $type = 'service'; + } else { + $type = 'host'; + } + + $customvars = $this->blacklistedProperties->removeMatching( + [$type => ['vars' => array_flip($customvars)]] + ); + + $customvars = isset($customvars[$type]['vars']) ? array_flip($customvars[$type]['vars']) : []; + + if ($this->protectedCustomvars === null) { + $config = Config::module('monitoring')->get('security', 'protected_customvars'); + $protectedCustomvars = array(); + + foreach (preg_split('~,~', $config, -1, PREG_SPLIT_NO_EMPTY) as $pattern) { + $regex = array(); + foreach (explode('*', $pattern) as $literal) { + $regex[] = preg_quote($literal, '/'); + } + + $protectedCustomvars[] = implode('.*', $regex); + } + + $this->protectedCustomvars = empty($protectedCustomvars) + ? '/^$/' + : '/^(?:' . implode('|', $protectedCustomvars) . ')$/'; + } + + return preg_grep($this->protectedCustomvars, $customvars, PREG_GREP_INVERT); + } +} diff --git a/library/Cube/Ido/IdoHostStatusCube.php b/library/Cube/Ido/IdoHostStatusCube.php new file mode 100644 index 0000000..3f79dc8 --- /dev/null +++ b/library/Cube/Ido/IdoHostStatusCube.php @@ -0,0 +1,97 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2016 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido; + +use Icinga\Module\Cube\CubeRenderer\HostStatusCubeRenderer; +use Icinga\Module\Cube\Ido\DataView\Hoststatus; + +class IdoHostStatusCube extends IdoCube +{ + public function getRenderer() + { + return new HostStatusCubeRenderer($this); + } + + /** + * @inheritdoc + */ + public function getAvailableFactColumns() + { + return array( + 'hosts_cnt' => 'SUM(CASE WHEN hs.has_been_checked = 1 THEN 1 ELSE 0 END)', + 'hosts_down' => 'SUM(CASE WHEN hs.has_been_checked = 1 AND hs.current_state = 1' + . ' THEN 1 ELSE 0 END)', + 'hosts_unhandled_down' => 'SUM(CASE WHEN hs.has_been_checked = 1 AND hs.current_state = 1' + . ' AND hs.problem_has_been_acknowledged = 0 AND hs.scheduled_downtime_depth = 0' + . ' THEN 1 ELSE 0 END)', + 'hosts_unreachable' => 'SUM(CASE WHEN hs.current_state = 2 THEN 1 ELSE 0 END)', + 'hosts_unhandled_unreachable' => 'SUM(CASE WHEN hs.current_state = 2' + . ' AND hs.problem_has_been_acknowledged = 0 AND hs.scheduled_downtime_depth = 0' + . ' THEN 1 ELSE 0 END)', + ); + } + + public function createDimension($name) + { + $this->registerAvailableDimensions(); + + if (isset($this->availableDimensions[$name])) { + return clone $this->availableDimensions[$name]; + } + + return new CustomVarDimension($name, CustomVarDimension::TYPE_HOST); + } + + /** + * This returns a list of all available Dimensions + * + * @return array + */ + public function listAvailableDimensions() + { + $this->requireBackend(); + + $view = $this->backend->select()->from('hoststatus'); + + $view->applyFilter($this->getMonitoringRestriction()); + + $select = $view->getQuery()->clearOrder()->getSelectQuery(); + + $select + ->columns('cv.varname') + ->join( + ['cv' => $this->tableName('icinga_customvariablestatus')], + 'cv.object_id = ho.object_id', + [] + ) + ->group('cv.varname'); + + if (version_compare($this->getIdoVersion(), '1.12.0', '>=')) { + $select->where('cv.is_json = 0'); + } + + $select->order('cv.varname'); + + $dimensions = $this->filterProtectedCustomvars($this->db()->fetchCol($select)); + $keys = array_map('strtolower', $dimensions); + + return array_combine($keys, $dimensions); + } + + public function prepareInnerQuery() + { + $this->requireBackend(); + + $view = new Hoststatus($this->backend); + + $view->getQuery()->requireColumn('host_state'); + + $view->applyFilter($this->getMonitoringRestriction()); + + $select = $view->getQuery()->clearOrder()->getSelectQuery(); + + return $select; + } +} diff --git a/library/Cube/Ido/IdoServiceStatusCube.php b/library/Cube/Ido/IdoServiceStatusCube.php new file mode 100644 index 0000000..a403645 --- /dev/null +++ b/library/Cube/Ido/IdoServiceStatusCube.php @@ -0,0 +1,97 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2019 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido; + +use Icinga\Module\Cube\CubeRenderer\ServiceStatusCubeRenderer; + +class IdoServiceStatusCube extends IdoCube +{ + public function getRenderer() + { + return new ServiceStatusCubeRenderer($this); + } + + public function getAvailableFactColumns() + { + return [ + 'services_cnt' => 'SUM(CASE WHEN ss.has_been_checked = 1 THEN 1 ELSE 0 END)', + 'services_critical' => 'SUM(CASE WHEN ss.has_been_checked = 1 AND ss.current_state = 2' + . ' THEN 1 ELSE 0 END)', + 'services_unhandled_critical' => 'SUM(CASE WHEN ss.has_been_checked = 1 AND ss.current_state = 2' + . ' AND ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0' + . ' THEN 1 ELSE 0 END)', + 'services_warning' => 'SUM(CASE WHEN ss.current_state = 1 THEN 1 ELSE 0 END)', + 'services_unhandled_warning' => 'SUM(CASE WHEN ss.current_state = 1' + . ' AND ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0' + . ' THEN 1 ELSE 0 END)', + 'services_unknown' => 'SUM(CASE WHEN ss.current_state = 3 THEN 1 ELSE 0 END)', + 'services_unhandled_unknown' => 'SUM(CASE WHEN ss.current_state = 3' + . ' AND ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0' + . ' THEN 1 ELSE 0 END)', + ]; + } + + /** + * This returns a list of all available Dimensions + * + * @return array + */ + public function listAvailableDimensions() + { + $this->requireBackend(); + + $view = $this->backend->select()->from('servicestatus'); + + $view->applyFilter($this->getMonitoringRestriction()); + + $select = $view->getQuery()->clearOrder()->getSelectQuery(); + + $select + ->columns('cv.varname') + ->join( + ['cv' => $this->tableName('icinga_customvariablestatus')], + 'cv.object_id = so.object_id', + [] + ) + ->group('cv.varname'); + + if (version_compare($this->getIdoVersion(), '1.12.0', '>=')) { + $select->where('cv.is_json = 0'); + } + + $select->order('cv.varname'); + + $dimensions = $this->filterProtectedCustomvars($this->db()->fetchCol($select)); + $keys = array_map('strtolower', $dimensions); + + return array_combine($keys, $dimensions); + } + + public function prepareInnerQuery() + { + $this->requireBackend(); + + $view = $this->backend->select()->from('servicestatus'); + + $view->getQuery()->requireColumn('service_state'); + + $view->applyFilter($this->getMonitoringRestriction()); + + $select = $view->getQuery()->clearOrder()->getSelectQuery(); + + return $select; + } + + public function createDimension($name) + { + $this->registerAvailableDimensions(); + + if (isset($this->availableDimensions[$name])) { + return clone $this->availableDimensions[$name]; + } + + return new CustomVarDimension($name, CustomVarDimension::TYPE_SERVICE); + } +} diff --git a/library/Cube/Ido/Query/HoststatusQuery.php b/library/Cube/Ido/Query/HoststatusQuery.php new file mode 100644 index 0000000..6a9aa96 --- /dev/null +++ b/library/Cube/Ido/Query/HoststatusQuery.php @@ -0,0 +1,47 @@ +<?php + +// Icinga Web 2 Cube Module | (c) 2021 Icinga GmbH | GPLv2 + +namespace Icinga\Module\Cube\Ido\Query; + +use Exception; +use Icinga\Application\Version; +use Icinga\Data\Filter\FilterExpression; +use Icinga\Exception\NotImplementedError; +use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery; + +class HoststatusQuery extends \Icinga\Module\Monitoring\Backend\Ido\Query\HoststatusQuery +{ + protected $subQueryTargets = array( + 'hostgroups' => 'hostgroup', + 'servicegroups' => 'servicegroup', + 'services' => 'servicestatus' + ); + + protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter) + { + if ($name === 'servicestatus') { + return ['s.host_object_id', 'ho.object_id']; + } + + return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter); + } + + protected function createSubQueryFilter(FilterExpression $filter, $queryName) + { + try { + return parent::createSubQueryFilter($filter, $queryName); + } catch (Exception $e) { + if (version_compare(Version::VERSION, '2.10.0', '>=')) { + throw $e; + } + + if ($e->getMessage() === 'Undefined array key 0' && basename($e->getFile()) === 'IdoQuery.php') { + // Ensures compatibility with earlier Icinga Web 2 versions + throw new NotImplementedError(''); + } else { + throw $e; + } + } + } +} diff --git a/library/Cube/Ido/ZfSelectWrapper.php b/library/Cube/Ido/ZfSelectWrapper.php new file mode 100644 index 0000000..745d7a5 --- /dev/null +++ b/library/Cube/Ido/ZfSelectWrapper.php @@ -0,0 +1,77 @@ +<?php + +namespace Icinga\Module\Cube\Ido; + +/** + * Since version 1.1.0 we're using the monitoring module's queries as the cubes' base queries. + * Before, the host object table was available using the alias 'o'. Now it's 'ho'. + * Without this wrapper, the action link hook provided by the director would fail because it relies on the alias 'o'. + */ +class ZfSelectWrapper +{ + /** @var \Zend_Db_Select */ + protected $select; + + public function __construct(\Zend_Db_Select $select) + { + $this->select = $select; + } + + /** + * Get the underlying Zend_Db_Select query + * + * @return \Zend_Db_Select + */ + public function unwrap() + { + return $this->select; + } + + /** + * {@see \Zend_Db_Select::reset()} + */ + public function reset($part = null) + { + $this->select->reset($part); + + return $this; + } + + /** + * {@see \Zend_Db_Select::columns()} + */ + public function columns($cols = '*', $correlationName = null) + { + if (is_array($cols)) { + foreach ($cols as $alias => &$col) { + if (substr($col, 0, 2) === 'o.') { + $col = 'ho.' . substr($col, 2); + } + } + } + + return $this->select->columns($cols, $correlationName); + } + + /** + * Proxy Zend_Db_Select method calls + * + * @param string $name The name of the method to call + * @param array $arguments Arguments for the method to call + * + * @return mixed + * + * @throws \BadMethodCallException If the called method does not exist + */ + public function __call($name, array $arguments) + { + if (! method_exists($this->select, $name)) { + $class = get_class($this); + $message = "Call to undefined method $class::$name"; + + throw new \BadMethodCallException($message); + } + + return call_user_func_array([$this->select, $name], $arguments); + } +} |