diff --git a/application/controllers/FilterController.php b/application/controllers/FilterController.php index 00e598ea9..f79b7ed53 100644 --- a/application/controllers/FilterController.php +++ b/application/controllers/FilterController.php @@ -72,7 +72,6 @@ class FilterController extends ActionController $this->getParam('filter_module') ); $urlTarget = $this->parse($query, $target); - die(print_r($urlTarget,true)); $this->redirect($urlTarget['urlParam']); } @@ -108,7 +107,8 @@ class FilterController extends ActionController return array( 'state' => 'success', 'proposals' => $this->registry->getProposalsForQuery($text), - 'urlParam' => $registry::getUrlForTarget($target, $queryTree) + 'urlParam' => $registry::getUrlForTarget($target, $queryTree), + 'valid' => count($this->registry->getIgnoredQueryParts()) === 0 ); } catch (\Exception $exc) { Logger::error($exc); diff --git a/library/Icinga/Data/Db/TreeToSqlParser.php b/library/Icinga/Data/Db/TreeToSqlParser.php index 79de0e53d..4baedf644 100644 --- a/library/Icinga/Data/Db/TreeToSqlParser.php +++ b/library/Icinga/Data/Db/TreeToSqlParser.php @@ -66,13 +66,22 @@ class TreeToSqlParser * @param String $operator The operator from the query node * @return string The operator for the sql query part */ - private function getSqlOperator($operator) + private function getSqlOperator($operator, array $right) { + switch($operator) { case Node::OPERATOR_EQUALS: - return 'LIKE'; + if (count($right) > 1) { + return 'IN'; + } else { + return 'LIKE'; + } case Node::OPERATOR_EQUALS_NOT: - return 'NOT LIKE'; + if (count($right) > 1) { + return 'NOT IN'; + } else { + return 'NOT LIKE'; + } default: return $operator; } @@ -125,14 +134,14 @@ class TreeToSqlParser return ''; } $this->query->requireColumn($node->left); - $queryString = $this->query->getMappedField($node->left); + $queryString = '(' . $this->query->getMappedField($node->left) . ')'; if ($this->query->isAggregateColumn($node->left)) { $this->type = 'HAVING'; } $queryString .= ' ' . (is_integer($node->right) ? - $node->operator : $this->getSqlOperator($node->operator)) . ' '; - $queryString .= $this->getParameterValue($node); + $node->operator : $this->getSqlOperator($node->operator, $node->right)) . ' '; + $queryString = $this->addValueToQuery($node, $queryString); return $queryString; } @@ -145,18 +154,27 @@ class TreeToSqlParser * @param Node $node The node to retrieve the sql string value from * @return String|int The converted and quoted value */ - private function getParameterValue(Node $node) { - $value = $node->right; - if ($node->operator === Node::OPERATOR_EQUALS || $node->operator === Node::OPERATOR_EQUALS_NOT) { - $value = str_replace('*', '%', $value); + private function addValueToQuery(Node $node, $query) { + $values = array(); + + foreach ($node->right as $value) { + if ($node->operator === Node::OPERATOR_EQUALS || $node->operator === Node::OPERATOR_EQUALS_NOT) { + $value = str_replace('*', '%', $value); + } + if ($this->query->isTimestamp($node->left)) { + $node->context = Node::CONTEXT_TIMESTRING; + } + if ($node->context === Node::CONTEXT_TIMESTRING) { + $value = strtotime($value); + } + $values[] = $this->query->getDatasource()->getConnection()->quote($value); } - if ($this->query->isTimestamp($node->left)) { - $node->context = Node::CONTEXT_TIMESTRING; + $valueString = join(',', $values); + + if (count($values) > 1) { + return '( '. $valueString . ')'; } - if ($node->context === Node::CONTEXT_TIMESTRING) { - $value = strtotime($value); - } - return $this->query->getDatasource()->getConnection()->quote($value); + return $query . $valueString; } /** diff --git a/library/Icinga/Filter/Filter.php b/library/Icinga/Filter/Filter.php index 4d324ce74..b999d6bd4 100644 --- a/library/Icinga/Filter/Filter.php +++ b/library/Icinga/Filter/Filter.php @@ -186,7 +186,7 @@ class Filter extends QueryProposer } $proposals = array_merge($proposals, $this->getDefaultDomain()->getProposalsForQuery($query)); } - return $proposals; + return array_unique($proposals); } /** @@ -197,7 +197,7 @@ class Filter extends QueryProposer */ private function splitQueryAtNextConjunction($query) { - $delimiter = array('AND', 'OR'); + $delimiter = array('AND'/*, 'OR'*/); // or is not supported currently $inStr = false; for ($i = 0; $i < strlen($query); $i++) { // Skip strings diff --git a/library/Icinga/Filter/Query/Node.php b/library/Icinga/Filter/Query/Node.php index db82009c4..a287635c9 100644 --- a/library/Icinga/Filter/Query/Node.php +++ b/library/Icinga/Filter/Query/Node.php @@ -113,6 +113,14 @@ class Node $node->type = self::TYPE_OPERATOR; $node->operator = $operator; $node->left = $left; + if ($right === null) { + $right = array(); + } elseif (!is_array($right)) { + $right = array($right); + } + foreach($right as &$value) { + $value = trim($value); + } $node->right = $right; return $node; } diff --git a/library/Icinga/Filter/Query/Tree.php b/library/Icinga/Filter/Query/Tree.php index 49662cfb7..5c1690fff 100644 --- a/library/Icinga/Filter/Query/Tree.php +++ b/library/Icinga/Filter/Query/Tree.php @@ -54,6 +54,23 @@ class Tree */ private $lastNode; + public function insertTree(Tree $tree) + { + $this->insertSubTree($tree->root); + $this->root = $this->normalizeTree($this->root); + } + + private function insertSubTree(Node $node) + { + if ($node->type === Node::TYPE_OPERATOR) { + $this->insert($node); + } else { + $this->insert($node->type === Node::TYPE_AND ? Node::createAndNode() : Node::createOrNode()); + $this->insertSubTree($node->left); + $this->insertSubTree($node->right); + } + } + /** * Insert a node into this tree, recognizing type and insert position * @@ -77,7 +94,6 @@ class Tree $this->insert(Node::createAndNode()); } $node->parent = $this->lastNode; - if ($this->lastNode->left == null) { $this->lastNode->left = $node; } elseif ($this->lastNode->right == null) { @@ -86,6 +102,7 @@ class Tree break; } } + $this->lastNode = $node; } @@ -350,16 +367,23 @@ class Tree if ($ctx === null) { return null; } - if ($ctx->type === Node::TYPE_OPERATOR) { - if ($ctx->left == $node->left && $ctx->right == $node->right && $ctx->operator == $node->operator) { - return $ctx; + + if ($ctx->type == Node::TYPE_OPERATOR) { + if ($ctx->left == $node->left && $ctx->operator == $node->operator) { + if(empty($node->right) || $ctx->right == $node->right) { + return $ctx; + } } return null; } else { - $result = $this->findNode($node, $ctx->left); - if ($result === null) { + $result = null; + if ($ctx->left) { + $result = $this->findNode($node, $ctx->left); + } if ($result == null && $ctx->right) { $result = $this->findNode($node, $ctx->right); + } + return $result; } } @@ -367,21 +391,25 @@ class Tree /** * Return true if A node with the given attribute on the left side exists * - * @param String $name The attribute to test for existence - * @param Node $ctx The current root node + * @param String $name The attribute to test for existence + * @param Node $ctx The current root node + * @oaram bool $isRecursive Internal flag to disable null nodes being replaced with the tree root * * @return bool True if a node contains $name on the left side, otherwise false */ - public function hasNodeWithAttribute($name, $ctx = null) + public function hasNodeWithAttribute($name, $ctx = null, $isRecursive = false) { - $ctx = $ctx ? $ctx : $this->root; + if (!$isRecursive) { + $ctx = $ctx ? $ctx : $this->root; + } if ($ctx === null) { return false; } if ($ctx->type === Node::TYPE_OPERATOR) { return $ctx->left === $name; } else { - return $this->hasNodeWithAttribute($name, $ctx->left) || $this->hasNodeWithAttribute($name, $ctx->right); + return $this->hasNodeWithAttribute($name, $ctx->left, true) + || $this->hasNodeWithAttribute($name, $ctx->right, true); } } } diff --git a/library/Icinga/Protocol/Statusdat/Query.php b/library/Icinga/Protocol/Statusdat/Query.php index 4f844a896..85742e8ad 100755 --- a/library/Icinga/Protocol/Statusdat/Query.php +++ b/library/Icinga/Protocol/Statusdat/Query.php @@ -225,7 +225,8 @@ class Query extends BaseQuery $indexes = array_keys($state[$target]); if ($baseGroup) { $baseGroup->setQuery($this); - $indexes = $baseGroup->filter($state[$target]); + $idx = array_keys($state[$target]); + $indexes = $baseGroup->filter($state[$target], $idx ); } if (!isset($result[$target])) { $result[$target] = $indexes; diff --git a/library/Icinga/Protocol/Statusdat/Query/Expression.php b/library/Icinga/Protocol/Statusdat/Query/Expression.php index 1087eabca..659614d3f 100755 --- a/library/Icinga/Protocol/Statusdat/Query/Expression.php +++ b/library/Icinga/Protocol/Statusdat/Query/Expression.php @@ -28,6 +28,8 @@ namespace Icinga\Protocol\Statusdat\Query; +use Icinga\Protocol\Ldap\Exception; + class Expression implements IQueryPart { /** @@ -117,12 +119,18 @@ class Expression implements IQueryPart case "LIKE": $this->CB = "isLike"; break; + case "NOT_LIKE": + $this->CB = "isNotLike"; + break; case "!=": $this->CB = "isNotEqual"; break; case "IN": $this->CB = "isIn"; break; + case "NOT_IN": + $this->CB = "isNotIn"; + break; default: throw new \Exception("Unknown operator $token in expression $this->expression !"); } @@ -216,7 +224,6 @@ class Expression implements IQueryPart $idx = array_keys($base); } $this->basedata = $base; - return array_filter($idx, array($this, "filterFn")); } @@ -248,12 +255,24 @@ class Expression implements IQueryPart return false; } - if ($this->CB == "isIn") { - return count(array_intersect($values, $this->value)) > 0; - } - if ($this->CB == "isNotIn") { - return count(array_intersect($values, $this->value)) == 0; + if ($this->CB == "isIn" || $this->CB == "isNotIn") { + $cmpValues = is_array($this->value) ? $this->value : array($this->value); + foreach ($cmpValues as $cmpValue) { + $this->value = $cmpValue; + foreach ($values as $value) { + if ($this->CB == "isIn" && $this->isLike($value)) { + $this->value = $cmpValues; + return true; + } elseif ($this->CB == "isNotIn" && $this->isNotLike($value)) { + $this->value = $cmpValues; + return true; + } + } + } + $this->value = $cmpValues; + return false; } + if ($this->function) { $values = call_user_func($this->function, $values); if (!is_array($values)) { @@ -355,6 +374,15 @@ class Expression implements IQueryPart return preg_match("/^" . str_replace("%", ".*", $this->value) . "$/", $value) ? true : false; } + /** + * @param $value + * @return bool + */ + public function isNotLike($value) + { + return !preg_match("/^" . str_replace("%", ".*", $this->value) . "$/", $value) ? true : false; + } + /** * @param $value * @return bool diff --git a/library/Icinga/Protocol/Statusdat/TreeToStatusdatQueryParser.php b/library/Icinga/Protocol/Statusdat/TreeToStatusdatQueryParser.php index f1a2dcf17..13d1eab15 100644 --- a/library/Icinga/Protocol/Statusdat/TreeToStatusdatQueryParser.php +++ b/library/Icinga/Protocol/Statusdat/TreeToStatusdatQueryParser.php @@ -26,30 +26,48 @@ */ // {{{ICINGA_LICENSE_HEADER}}} - namespace Icinga\Protocol\Statusdat; - use Icinga\Filter\Filterable; use Icinga\Filter\Query\Node; use Icinga\Filter\Query\Tree; use Icinga\Protocol\Statusdat\Query\Expression; use Icinga\Protocol\Statusdat\Query\Group; +use Icinga\Protocol\Statusdat\Query\IQueryPart; +/** + * Parser to create statusdat filter expressions from query trees + * + */ class TreeToStatusdatQueryParser { - private function nodeToQuery(Node $node, Filterable $source) + /** + * Create a Statusdat expression from a Tree node + * + * @param Node $node The node to convert to an expression + * @param Filterable $source The filterable to use for field mapping + * + * @return IQueryPart Either a statusdat expression or an expression group + */ + private function nodeToQuery(Node $node, Filterable $source) { if ($node->type === Node::TYPE_OPERATOR) { $op = $node->operator; - $value = $node->right; + $node->left = $source->getMappedField($node->left); - if (stripos($node->right, '*') !== false) { - $op = 'LIKE'; + $op = 'IN'; + $values = $node->right; + + if ($node->operator === NODE::OPERATOR_EQUALS_NOT) { + $op = 'NOT_' . $op; + + } + foreach ($values as &$value) { $value = str_replace('*', '%', $value); } - return new Expression($node->left . ' ' . $op . ' ?', $value); + $values = array($values); + return new Expression($node->left . ' ' . $op . ' ? ', $values); } else { $group = new Group(); $group->setType(($node->type === Node::TYPE_OR) ? Group::TYPE_OR : Group::TYPE_AND); @@ -60,6 +78,14 @@ class TreeToStatusdatQueryParser } + /** + * Create a statusdat specific filter expression for the given query tree and filterable + * + * @param Tree $tree The tree to convert to a query + * @param Filterable $source The filterable to use for tree conversion + * + * @return IQueryPart A statusdat query object + */ public function treeToQuery(Tree $tree, Filterable $source) { @@ -70,4 +96,4 @@ class TreeToStatusdatQueryParser } return null; } -} \ No newline at end of file +} diff --git a/library/Icinga/Web/Widget/FilterBadgeRenderer.php b/library/Icinga/Web/Widget/FilterBadgeRenderer.php index aa274f89f..f98f12c51 100644 --- a/library/Icinga/Web/Widget/FilterBadgeRenderer.php +++ b/library/Icinga/Web/Widget/FilterBadgeRenderer.php @@ -48,6 +48,29 @@ class FilterBadgeRenderer implements Widget private $conjunctionCellar = ''; private $urlFilter; + private $tpl =<<<'EOT' +