mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-27 07:44:04 +02:00
Data\Filter: initial commit basic implementation
Basic operators are there, still missing: subclassed "where" to distinct comparison operators like greater/less than on a class level. refs #6418
This commit is contained in:
parent
d44a87717d
commit
f438cb30e1
188
library/Icinga/Data/Filter/Filter.php
Normal file
188
library/Icinga/Data/Filter/Filter.php
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter
|
||||||
|
*
|
||||||
|
* Base class for filters (why?) and factory for the different FilterOperators
|
||||||
|
*/
|
||||||
|
class Filter
|
||||||
|
{
|
||||||
|
protected $id = '1';
|
||||||
|
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById($id)
|
||||||
|
{
|
||||||
|
if ($id === $this->getId()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
throw new Exception(sprintf(
|
||||||
|
'Trying to get invalid filter index "%s" from "%s"', $id, $this
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasId($id)
|
||||||
|
{
|
||||||
|
if ($id === $this->getId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where Filter factory
|
||||||
|
*
|
||||||
|
* @param string $col Column to be filtered
|
||||||
|
* @param string $filter Filter expression
|
||||||
|
*
|
||||||
|
* @throws FilterException
|
||||||
|
* @return FilterWhere
|
||||||
|
*/
|
||||||
|
public static function where($col, $filter)
|
||||||
|
{
|
||||||
|
return new FilterWhere($col, $filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Or FilterOperator factory
|
||||||
|
*
|
||||||
|
* @param Filter $filter,... Unlimited optional list of Filters
|
||||||
|
*
|
||||||
|
* @return FilterOr
|
||||||
|
*/
|
||||||
|
public static function matchAny()
|
||||||
|
{
|
||||||
|
return new FilterOr(func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Or FilterOperator factory
|
||||||
|
*
|
||||||
|
* @param Filter $filter,... Unlimited optional list of Filters
|
||||||
|
*
|
||||||
|
* @return FilterAnd
|
||||||
|
*/
|
||||||
|
public static function matchAll()
|
||||||
|
{
|
||||||
|
return new FilterAnd(func_get_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilterNot factory, negates the given filter
|
||||||
|
*
|
||||||
|
* @param Filter $filter Filter to be negated
|
||||||
|
*
|
||||||
|
* @return FilterNot
|
||||||
|
*/
|
||||||
|
public static function not(Filter $filter)
|
||||||
|
{
|
||||||
|
return new FilterNot(array($filter)); // ??
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create filter from queryString
|
||||||
|
*
|
||||||
|
* This is still pretty basic, need improvement
|
||||||
|
*/
|
||||||
|
public static function fromQueryString($query)
|
||||||
|
{
|
||||||
|
$query = rawurldecode($query);
|
||||||
|
$parts = preg_split('~&~', $query, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
$filters = Filter::matchAll();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
self::parseQueryStringPart($part, $filters);
|
||||||
|
}
|
||||||
|
return $filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse query string part
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected static function parseQueryStringPart($part, & $filters)
|
||||||
|
{
|
||||||
|
$negations = 0;
|
||||||
|
|
||||||
|
if (strpos($part, '=') === false) {
|
||||||
|
|
||||||
|
$key = rawurldecode($part);
|
||||||
|
|
||||||
|
while (substr($key, 0, 1) === '!') {
|
||||||
|
if (strlen($key) < 2) {
|
||||||
|
throw new FilterException(
|
||||||
|
sprintf('Got invalid filter part: "%s"', $part)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$key = substr($key, 1);
|
||||||
|
$negations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = Filter::where($key, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
list($key, $val) = preg_split('/=/', $part, 2);
|
||||||
|
$key = rawurldecode($key);
|
||||||
|
$val = rawurldecode($val);
|
||||||
|
|
||||||
|
while (substr($key, 0, 1) === '!') {
|
||||||
|
if (strlen($key) < 2) {
|
||||||
|
throw new FilterException(
|
||||||
|
sprintf('Got invalid filter part: "%s"', $part)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$key = substr($key, 1);
|
||||||
|
$negations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (substr($key, -1) === '!') {
|
||||||
|
if (strlen($key) < 2) {
|
||||||
|
throw new FilterException(
|
||||||
|
sprintf('Got invalid filter part: "%s"', $part)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$key = substr($key, 0, -1);
|
||||||
|
$negations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($val, '|') !== false) {
|
||||||
|
$vals = preg_split('/\|/', $val, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
$filter = Filter::matchAny();
|
||||||
|
foreach ($vals as $val) {
|
||||||
|
$filter->addFilter(Filter::where($key, $val));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$filter = Filter::where($key, $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($negations % 2 === 0) {
|
||||||
|
$filters->addFilter($filter);
|
||||||
|
} else {
|
||||||
|
$filters->addFilter(Filter::not($filter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need a new Querystring-Parser
|
||||||
|
*
|
||||||
|
* Still TBD, should be able to read such syntax:
|
||||||
|
* (host_name=test&(service=ping|(service=http&host=*net*)))
|
||||||
|
*/
|
||||||
|
protected static function consumeStringUnless(& $string, $stop)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
31
library/Icinga/Data/Filter/FilterAnd.php
Normal file
31
library/Icinga/Data/Filter/FilterAnd.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter list AND
|
||||||
|
*
|
||||||
|
* Binary AND, all contained filters must succeed
|
||||||
|
*/
|
||||||
|
class FilterAnd extends FilterOperator
|
||||||
|
{
|
||||||
|
protected $operatorName = 'AND';
|
||||||
|
|
||||||
|
protected $operatorSymbol = '&';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the given row object matches this filter
|
||||||
|
*
|
||||||
|
* @object $row
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function matches($row)
|
||||||
|
{
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
if (! $filter->matches($row)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
12
library/Icinga/Data/Filter/FilterException.php
Normal file
12
library/Icinga/Data/Filter/FilterException.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Exception Class
|
||||||
|
*
|
||||||
|
* Filter Exceptions should be thrown on filter parse errors or similar
|
||||||
|
*/
|
||||||
|
class FilterException extends Exception {}
|
48
library/Icinga/Data/Filter/FilterNot.php
Normal file
48
library/Icinga/Data/Filter/FilterNot.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
class FilterNot extends FilterOperator
|
||||||
|
{
|
||||||
|
protected $operatorName = 'NOT';
|
||||||
|
|
||||||
|
protected $operatorSymbol = '!'; // BULLSHIT
|
||||||
|
|
||||||
|
// TODO: Max count 1 or autocreate sub-and?
|
||||||
|
|
||||||
|
public function matches($row)
|
||||||
|
{
|
||||||
|
foreach ($this->filters() as $filter) {
|
||||||
|
if ($filter->matches($row)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toQueryString()
|
||||||
|
{
|
||||||
|
$parts = array();
|
||||||
|
if (empty($this->filters)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
foreach ($this->filters() as $filter) {
|
||||||
|
$parts[] = $filter->toQueryString();
|
||||||
|
}
|
||||||
|
if (count($parts) === 1) {
|
||||||
|
return '!' . $parts[0];
|
||||||
|
} else {
|
||||||
|
return '!(' . implode('&', $parts) . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
$sub = Filter::matchAll();
|
||||||
|
var_dump($this->filters());
|
||||||
|
foreach ($this->filters() as $f) {
|
||||||
|
$sub->addFilter($f);
|
||||||
|
}
|
||||||
|
return '! (' . $sub . ')';
|
||||||
|
}
|
||||||
|
}
|
157
library/Icinga/Data/Filter/FilterOperator.php
Normal file
157
library/Icinga/Data/Filter/FilterOperator.php
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilterOperator
|
||||||
|
*
|
||||||
|
* A FilterOperator contains a list ...
|
||||||
|
*/
|
||||||
|
abstract class FilterOperator extends Filter
|
||||||
|
{
|
||||||
|
protected $filters = array();
|
||||||
|
|
||||||
|
protected $operatorName;
|
||||||
|
|
||||||
|
protected $operatorSymbol;
|
||||||
|
|
||||||
|
public function hasId($id)
|
||||||
|
{
|
||||||
|
foreach ($this->filters() as $filter) {
|
||||||
|
if ($filter->hasId($id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parent::hasId($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById($id)
|
||||||
|
{
|
||||||
|
foreach ($this->filters() as $filter) {
|
||||||
|
if ($filter->hasId($id)) {
|
||||||
|
return $filter->getById($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parent::getById($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeId($id)
|
||||||
|
{
|
||||||
|
if ($id === $this->getId()) {
|
||||||
|
$this->filters = array();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
$remove = null;
|
||||||
|
foreach ($this->filters as $key => $filter) {
|
||||||
|
if ($filter->getId() === $id) {
|
||||||
|
$remove = $key;
|
||||||
|
} elseif ($filter instanceof FilterOperator) {
|
||||||
|
$filter->removeId($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($remove !== null) {
|
||||||
|
unset($this->filters[$remove]);
|
||||||
|
$this->filters = array_values($this->filters);
|
||||||
|
}
|
||||||
|
$this->refreshChildIds();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function refreshChildIds()
|
||||||
|
{
|
||||||
|
$i = 0;
|
||||||
|
$id = $this->getId();
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
$i++;
|
||||||
|
$filter->setId($id . '-' . $i);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
return parent::setId($id)->refreshChildIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOperatorName()
|
||||||
|
{
|
||||||
|
return $this->operatorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOperatorSymbol()
|
||||||
|
{
|
||||||
|
return $this->operatorSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toQueryString()
|
||||||
|
{
|
||||||
|
$parts = array();
|
||||||
|
if (empty($this->filters)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
foreach ($this->filters() as $filter) {
|
||||||
|
$parts[] = $filter->toQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: getLevel??
|
||||||
|
if (strpos($this->getId(), '-')) {
|
||||||
|
return '(' . implode($this->getOperatorSymbol(), $parts) . ')';
|
||||||
|
} else {
|
||||||
|
return implode($this->getOperatorSymbol(), $parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get simple string representation
|
||||||
|
*
|
||||||
|
* Useful for debugging only
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
if (empty($this->filters)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$parts = array();
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
if ($filter instanceof FilterOperator) {
|
||||||
|
$parts[] = '(' . $filter . ')';
|
||||||
|
} else {
|
||||||
|
$parts[] = (string) $filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$op = ' ' . $this->getOperatorSymbol() . ' ';
|
||||||
|
return implode($op, $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct($filters = array())
|
||||||
|
{
|
||||||
|
foreach ($filters as $filter) {
|
||||||
|
$this->addFilter($filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty()
|
||||||
|
{
|
||||||
|
return empty($this->filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFilter(Filter $filter)
|
||||||
|
{
|
||||||
|
$this->filters[] = $filter;
|
||||||
|
$filter->setId($this->getId() . '-' . (count($this->filters)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function &filters()
|
||||||
|
{
|
||||||
|
return $this->filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
foreach ($this->filters as & $filter) {
|
||||||
|
$filter = clone $filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
library/Icinga/Data/Filter/FilterOr.php
Normal file
20
library/Icinga/Data/Filter/FilterOr.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
class FilterOr extends FilterOperator
|
||||||
|
{
|
||||||
|
protected $operatorName = 'OR';
|
||||||
|
|
||||||
|
protected $operatorSymbol = '|';
|
||||||
|
|
||||||
|
public function matches($row)
|
||||||
|
{
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
if ($filter->matches($row)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
72
library/Icinga/Data/Filter/FilterWhere.php
Normal file
72
library/Icinga/Data/Filter/FilterWhere.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Icinga\Data\Filter;
|
||||||
|
|
||||||
|
class FilterWhere extends Filter
|
||||||
|
{
|
||||||
|
protected $column;
|
||||||
|
protected $expression;
|
||||||
|
|
||||||
|
public function __construct($column, $expression)
|
||||||
|
{
|
||||||
|
$this->column = $column;
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColumn()
|
||||||
|
{
|
||||||
|
return $this->column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setColumn($column)
|
||||||
|
{
|
||||||
|
$this->column = $column;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpression()
|
||||||
|
{
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
if (is_array($this->expression)) {
|
||||||
|
return $this->column . ' = ( ' . implode(' | ', $this->expression) . ' )';
|
||||||
|
} else {
|
||||||
|
return $this->column . ' = ' . $this->expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toQueryString()
|
||||||
|
{
|
||||||
|
if (is_array($this->expression)) {
|
||||||
|
return $this->column . '=' . implode('|', $this->expression);
|
||||||
|
} else {
|
||||||
|
return $this->column . '=' . $this->expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function matches($row)
|
||||||
|
{
|
||||||
|
if (is_array($this->expression)) {
|
||||||
|
return in_array($row->{$this->column}, $this->expression);
|
||||||
|
} elseif (strpos($this->expression, '*') === false) {
|
||||||
|
return (string) $row->{$this->column} === (string) $this->expression;
|
||||||
|
} else {
|
||||||
|
$parts = preg_split('~\*~', $this->expression);
|
||||||
|
foreach ($parts as & $part) {
|
||||||
|
$part = preg_quote($part);
|
||||||
|
}
|
||||||
|
$pattern = '/^' . implode('.*', $parts) . '$/';
|
||||||
|
return (bool) preg_match($pattern, $row->{$this->column});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
if (! $filter->matches($row)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -158,6 +158,8 @@ class FilterTest extends BaseTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Playing around to get ready for new queryString parser
|
||||||
public function testFromQueryString()
|
public function testFromQueryString()
|
||||||
{
|
{
|
||||||
$string = 'host_name=localhost&(service_state=1|service_state=2|service_state=3)&service_problem=1';
|
$string = 'host_name=localhost&(service_state=1|service_state=2|service_state=3)&service_problem=1';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user