icingaweb2/modules/monitoring/library/Monitoring/DataView/DataView.php

647 lines
16 KiB
PHP

<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Monitoring\DataView;
use IteratorAggregate;
use Icinga\Application\Hook;
use Icinga\Data\ConnectionInterface;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterMatch;
use Icinga\Data\FilterColumns;
use Icinga\Data\PivotTable;
use Icinga\Data\QueryInterface;
use Icinga\Data\SortRules;
use Icinga\Exception\QueryException;
use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Web\Request;
use Icinga\Web\Url;
/**
* A read-only view of an underlying query
*/
abstract class DataView implements QueryInterface, SortRules, FilterColumns, IteratorAggregate
{
/**
* The query used to populate the view
*
* @var IdoQuery
*/
protected $query;
protected $connection;
protected $isSorted = false;
/**
* The cache for all filter columns
*
* @var array
*/
protected $filterColumns;
/**
* Create a new view
*
* @param ConnectionInterface $connection
* @param array $columns
*/
public function __construct(ConnectionInterface $connection, array $columns = null)
{
$this->connection = $connection;
$this->query = $connection->query($this->getQueryName(), $columns);
}
/**
* Return a iterator for all rows of the result set
*
* @return IdoQuery
*/
public function getIterator()
{
return $this->getQuery();
}
/**
* Return the current position of the result set's iterator
*
* @return int
*/
public function getIteratorPosition()
{
return $this->query->getIteratorPosition();
}
/**
* Get the query name this data view relies on
*
* By default this is this class' name without its namespace
*
* @return string
*/
public static function getQueryName()
{
$tableName = explode('\\', get_called_class());
$tableName = end($tableName);
return $tableName;
}
public function where($condition, $value = null)
{
$this->query->where($condition, $value);
return $this;
}
public function dump()
{
if (! $this->isSorted) {
$this->order();
}
return $this->query->dump();
}
/**
* Retrieve columns provided by this view
*
* @return array
*/
abstract public function getColumns();
/**
* Create view from request
*
* @param Request $request
* @param array $columns
*
* @return static
* @deprecated Use $backend->select()->from($viewName) instead
*/
public static function fromRequest($request, array $columns = null)
{
$view = new static(MonitoringBackend::instance($request->getParam('backend')), $columns);
$view->applyUrlFilter($request);
return $view;
}
protected function getHookedColumns()
{
$columns = array();
foreach (Hook::all('monitoring/dataviewExtension') as $hook) {
foreach ($hook->getAdditionalQueryColumns($this->getQueryName()) as $col) {
$columns[] = $col;
}
}
return $columns;
}
// TODO: This is not the right place for this, move it away
protected function applyUrlFilter($request = null)
{
$url = Url::fromRequest();
$limit = $url->shift('limit');
$sort = $url->shift('sort');
$dir = $url->shift('dir');
$page = $url->shift('page');
$format = $url->shift('format');
$view = $url->shift('showCompact');
$view = $url->shift('backend');
foreach ($url->getParams() as $k => $v) {
$this->where($k, $v);
}
if ($sort) {
$this->order($sort, $dir);
}
}
/**
* Create view from params
*
* @param array $params
* @param array $columns
*
* @return static
*/
public static function fromParams(array $params, array $columns = null)
{
$view = new static(MonitoringBackend::instance($params['backend']), $columns);
foreach ($params as $key => $value) {
if ($view->isValidFilterTarget($key)) {
$view->where($key, $value);
}
}
if (isset($params['sort'])) {
$order = isset($params['order']) ? $params['order'] : null;
if ($order !== null) {
if (strtolower($order) === 'desc') {
$order = self::SORT_DESC;
} else {
$order = self::SORT_ASC;
}
}
$view->sort($params['sort'], $order);
}
return $view;
}
/**
* Check whether the given column is a valid filter column
*
* @param string $column
*
* @return bool
*/
public function isValidFilterTarget($column)
{
// Customvar
if ($column[0] === '_' && preg_match('/^_(?:host|service)_/i', $column)) {
return true;
}
return in_array($column, $this->getColumns()) || in_array($column, $this->getStaticFilterColumns());
}
/**
* Return all filter columns with their optional label as key
*
* This will merge the results of self::getColumns(), self::getStaticFilterColumns() and
* self::getDynamicFilterColumns() *once*. (i.e. subsequent calls of this function will
* return the same result.)
*
* @return array
*/
public function getFilterColumns()
{
if ($this->filterColumns === null) {
$columns = array_merge(
$this->getColumns(),
$this->getStaticFilterColumns(),
$this->getDynamicFilterColumns()
);
$this->filterColumns = array();
foreach ($columns as $label => $column) {
if (is_int($label)) {
$label = ucwords(str_replace('_', ' ', $column));
}
if ($this->query->isCaseInsensitive($column)) {
$label .= ' ' . t('(Case insensitive)');
}
$this->filterColumns[$label] = $column;
}
}
return $this->filterColumns;
}
/**
* Return all static filter columns
*
* @return array
*/
public function getStaticFilterColumns()
{
return array();
}
/**
* Return all dynamic filter columns such as custom variables
*
* @return array
*/
public function getDynamicFilterColumns()
{
$columns = array();
if (! $this->query->allowsCustomVars()) {
return $columns;
}
$query = MonitoringBackend::instance()
->select()
->from('customvar', array('varname', 'object_type'))
->where('is_json', 0)
->where('object_type_id', array(1, 2))
->getQuery()->group(array('varname', 'object_type'));
foreach ($query as $row) {
if ($row->object_type === 'host') {
$label = t('Host') . ' ' . ucwords(str_replace('_', ' ', $row->varname));
$columns[$label] = '_host_' . $row->varname;
} else { // $row->object_type === 'service'
$label = t('Service') . ' ' . ucwords(str_replace('_', ' ', $row->varname));
$columns[$label] = '_service_' . $row->varname;
}
}
return $columns;
}
/**
* Return the current filter
*
* @return Filter
*/
public function getFilter()
{
return $this->query->getFilter();
}
/**
* Return a pivot table for the given columns based on the current query
*
* @param string $xAxisColumn The column to use for the x axis
* @param string $yAxisColumn The column to use for the y axis
* @param Filter $xAxisFilter The filter to apply on a query for the x axis
* @param Filter $yAxisFilter The filter to apply on a query for the y axis
*
* @return PivotTable
*/
public function pivot($xAxisColumn, $yAxisColumn, Filter $xAxisFilter = null, Filter $yAxisFilter = null)
{
$pivot = new PivotTable($this->query, $xAxisColumn, $yAxisColumn);
return $pivot->setXAxisFilter($xAxisFilter)->setYAxisFilter($yAxisFilter);
}
/**
* Sort the rows, according to the specified sort column and order
*
* @param string $column Sort column
* @param string $order Sort order, one of the SORT_ constants
*
* @return $this
* @throws QueryException If the sort column is not allowed
* @see DataView::SORT_ASC
* @see DataView::SORT_DESC
* @deprecated Use DataView::order() instead
*/
public function sort($column = null, $order = null)
{
$sortRules = $this->getSortRules();
if ($column === null) {
// Use first available sort rule as default
if (empty($sortRules)) {
return $this;
}
$sortColumns = reset($sortRules);
if (! isset($sortColumns['columns'])) {
$sortColumns['columns'] = array(key($sortRules));
}
} else {
if (isset($sortRules[$column])) {
$sortColumns = $sortRules[$column];
if (! isset($sortColumns['columns'])) {
$sortColumns['columns'] = array($column);
}
} else {
$sortColumns = array(
'columns' => array($column),
'order' => $order
);
};
}
$order = $order === null ? (isset($sortColumns['order']) ? $sortColumns['order'] : static::SORT_ASC) : $order;
$order = (strtoupper($order) === static::SORT_ASC) ? 'ASC' : 'DESC';
foreach ($sortColumns['columns'] as $column) {
list($column, $direction) = $this->query->splitOrder($column);
if (! $this->isValidFilterTarget($column)) {
throw new QueryException(
mt('monitoring', 'The sort column "%s" is not allowed in "%s".'),
$column,
get_class($this)
);
}
$this->query->order($column, $direction !== null ? $direction : $order);
}
$this->isSorted = true;
return $this;
}
/**
* Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
*
* @return array
*/
public function getSortRules()
{
return array();
}
/**
* Sort result set either by the given column (and direction) or the sort defaults
*
* @param string $column
* @param string $direction
*
* @return $this
*/
public function order($column = null, $direction = null)
{
return $this->sort($column, $direction);
}
/**
* Whether an order is set
*
* @return bool
*/
public function hasOrder()
{
return $this->query->hasOrder();
}
/**
* Get the order if any
*
* @return array|null
*/
public function getOrder()
{
return $this->query->getOrder();
}
public function getMappedField($field)
{
return $this->query->getMappedField($field);
}
/**
* Return the query which was created in the constructor
*
* @return \Icinga\Data\SimpleQuery
*/
public function getQuery()
{
if (! $this->isSorted) {
$this->order();
}
return $this->query;
}
public function applyFilter(Filter $filter)
{
$this->validateFilterColumns($filter);
return $this->addFilter($filter);
}
/**
* Validates recursive the Filter columns against the isValidFilterTarget() method
*
* @param Filter $filter
*
* @throws \Icinga\Data\Filter\FilterException
*/
public function validateFilterColumns(Filter $filter)
{
if ($filter instanceof FilterMatch) {
if (! $this->isValidFilterTarget($filter->getColumn())) {
throw new QueryException(
mt('monitoring', 'The filter column "%s" is not allowed here.'),
$filter->getColumn()
);
}
}
if (method_exists($filter, 'filters')) {
foreach ($filter->filters() as $filter) {
$this->validateFilterColumns($filter);
}
}
}
public function clearFilter()
{
$this->query->clearFilter();
return $this;
}
/**
* @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
* column validation. Filter::matchAny() for the IdoQuery (or the DbQuery or the SimpleQuery I didn't have a look)
* is required for the filter to work properly.
*/
public function setFilter(Filter $filter)
{
$this->query->setFilter($filter);
return $this;
}
/**
* Get the view's search columns
*
* @return string[]
*/
public function getSearchColumns()
{
return array();
}
/**
* @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
* column validation.
*/
public function addFilter(Filter $filter)
{
$this->query->addFilter($filter);
return $this;
}
/**
* Count result set
*
* @return int
*/
public function count()
{
return $this->query->count();
}
/**
* Set whether the query should peek ahead for more results
*
* Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
* be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
*
* @return $this
*/
public function peekAhead($state = true)
{
$this->query->peekAhead($state);
return $this;
}
/**
* Return whether the query did not yield all available results
*
* @return bool
*/
public function hasMore()
{
return $this->query->hasMore();
}
/**
* Return whether this query will or has yielded any result
*
* @return bool
*/
public function hasResult()
{
return $this->query->hasResult();
}
/**
* Set a limit count and offset
*
* @param int $count Number of rows to return
* @param int $offset Start returning after this many rows
*
* @return self
*/
public function limit($count = null, $offset = null)
{
$this->query->limit($count, $offset);
return $this;
}
/**
* Whether a limit is set
*
* @return bool
*/
public function hasLimit()
{
return $this->query->hasLimit();
}
/**
* Get the limit if any
*
* @return int|null
*/
public function getLimit()
{
return $this->query->getLimit();
}
/**
* Whether an offset is set
*
* @return bool
*/
public function hasOffset()
{
return $this->query->hasOffset();
}
/**
* Get the offset if any
*
* @return int|null
*/
public function getOffset()
{
return $this->query->getOffset();
}
/**
* Retrieve an array containing all rows of the result set
*
* @return array
*/
public function fetchAll()
{
return $this->getQuery()->fetchAll();
}
/**
* Fetch the first row of the result set
*
* @return mixed
*/
public function fetchRow()
{
return $this->getQuery()->fetchRow();
}
/**
* Fetch the first column of all rows of the result set as an array
*
* @return array
*/
public function fetchColumn()
{
return $this->getQuery()->fetchColumn();
}
/**
* Fetch the first column of the first row of the result set
*
* @return string
*/
public function fetchOne()
{
return $this->getQuery()->fetchOne();
}
/**
* Fetch all rows of the result set as an array of key-value pairs
*
* The first column is the key, the second column is the value.
*
* @return array
*/
public function fetchPairs()
{
return $this->getQuery()->fetchPairs();
}
}