diff --git a/application/controllers/MigrateController.php b/application/controllers/MigrateController.php index 4978ccb1b..811b5d059 100644 --- a/application/controllers/MigrateController.php +++ b/application/controllers/MigrateController.php @@ -82,33 +82,16 @@ public function searchUrlAction() $this->httpBadRequest('No JSON content'); } - $traverseFilter = function ($filter) use (&$traverseFilter) { - if ($filter instanceof Filter\Chain) { - foreach ($filter as $child) { - $newChild = $traverseFilter($child); - if ($newChild !== null) { - $filter->replace($child, $newChild); - } - } - } elseif ($filter instanceof Filter\Equal) { - if (strpos($filter->getValue(), '*') !== false) { - return Filter::like($filter->getColumn(), $filter->getValue()); - } - } elseif ($filter instanceof Filter\Unequal) { - if (strpos($filter->getValue(), '*') !== false) { - return Filter::unlike($filter->getColumn(), $filter->getValue()); - } - } - }; - $urls = $this->getRequest()->getPost(); $result = []; foreach ($urls as $urlString) { $url = Url::fromPath($urlString); - $filter = QueryString::parse($url->getQueryString()); - $filter = $traverseFilter($filter) ?? $filter; - $result[] = rawurldecode($url->setParams([])->setFilter($filter)->getAbsoluteUrl()); + $params = $url->onlyWith(['sort', 'limit', 'view', 'columns', 'page'])->getParams(); + $filter = $url->without(['sort', 'limit', 'view', 'columns', 'page'])->getParams(); + $filter = QueryString::parse((string) $filter); + $filter = UrlMigrator::transformLegacyWildcardFilter($filter); + $result[] = rawurldecode($url->setParams($params)->setFilter($filter)->getAbsoluteUrl()); } $response = $this->getResponse()->json(); diff --git a/library/Icingadb/Compat/UrlMigrator.php b/library/Icingadb/Compat/UrlMigrator.php index 260cd015f..47780bed7 100644 --- a/library/Icingadb/Compat/UrlMigrator.php +++ b/library/Icingadb/Compat/UrlMigrator.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Icingadb\Compat; +use Icinga\Web\UrlParams; use InvalidArgumentException; use ipl\Stdlib\Filter; use ipl\Web\Filter\QueryString; @@ -47,6 +48,11 @@ public static function isSupportedUrl(Url $url): bool return isset($supportedPaths[ltrim($url->getPath(), '/')]); } + public static function hasParamTransformer(string $name): bool + { + return method_exists(new self(), $name . 'Parameters'); + } + public static function hasQueryTransformer(string $name): bool { return method_exists(new self(), $name . 'Columns'); @@ -58,22 +64,90 @@ public static function transformUrl(Url $url): Url throw new InvalidArgumentException(sprintf('Url path "%s" is not supported', $url->getPath())); } - list($queryTransformer, $dbRoute) = self::SUPPORTED_PATHS[ltrim($url->getPath(), '/')]; + list($transformer, $dbRoute) = self::SUPPORTED_PATHS[ltrim($url->getPath(), '/')]; $url = clone $url; $url->setPath($dbRoute); if (! $url->getParams()->isEmpty()) { - $filter = QueryString::parse((string) $url->getParams()); - $filter = self::transformFilter($filter, $queryTransformer); - if ($filter) { - $url->setParams([])->setFilter($filter); + [$params, $filter] = self::transformParams($url, $transformer); + $url->setParams($params); + + if (! $filter->isEmpty()) { + $filter = QueryString::parse((string) $filter); + $filter = self::transformFilter($filter, $transformer); + $url->setFilter($filter ?: null); } } return $url; } + public static function transformParams(Url $url, string $transformerName = null): array + { + $transformer = new self(); + + $params = self::commonParameters(); + $columns = self::commonColumns(); + + if ($transformerName !== null) { + if (! self::hasQueryTransformer($transformerName)) { + throw new InvalidArgumentException(sprintf('Transformer "%s" is not supported', $transformerName)); + } + + if (self::hasParamTransformer($transformerName)) { + $params = array_merge($params, $transformer->{$transformerName . 'Parameters'}()); + } + + $columns = array_merge($columns, $transformer->{$transformerName . 'Columns'}()); + } + + $columnRewriter = function ($column) use ($columns, $transformer) { + $rewritten = $transformer->rewrite(Filter::equal($column, 'bogus'), $columns); + if ($rewritten === false) { + return false; + } elseif ($rewritten instanceof Filter\Condition) { + return $rewritten->getColumn(); + } + + return $column; + }; + + $urlParams = $url->onlyWith(array_keys($params))->getParams(); + $urlFilter = $url->without(array_keys($params))->getParams(); + + $newParams = new UrlParams(); + foreach ($urlParams->toArray(false) as $name => $value) { + if (is_int($name)) { + $name = $value; + $value = true; + } else { + $value = rawurldecode($value); + } + + $name = rawurldecode($name); + + if (! isset($params[$name]) || $params[$name] === self::USE_EXPR) { + $newParams->add($name, $value); + } elseif ($params[$name] === self::DROP) { + // pass + } elseif (is_callable($params[$name])) { + $result = $params[$name]($value, $urlParams, $columnRewriter); + if ($result === false) { + continue; + } elseif (is_array($result)) { + [$name, $value] = $result; + } elseif ($result !== null) { + $value = $result; + } + + $newParams->add($name, $value); + } + } + + return [$newParams, $urlFilter]; + } + /** * Transform the given legacy filter * @@ -99,16 +173,39 @@ public static function transformFilter(Filter\Rule $filter, string $queryTransfo return $rewritten === false ? false : ($rewritten instanceof Filter\Rule ? $rewritten : $filter); } + /** + * Transform given legacy wildcard filters + * + * @param $filter Filter\Rule + * + * @return Filter\Chain|Filter\Condition + */ + public static function transformLegacyWildcardFilter(Filter\Rule $filter) + { + if ($filter instanceof Filter\Chain) { + foreach ($filter as $child) { + $newChild = self::transformLegacyWildcardFilter($child); + if ($newChild !== $child) { + $filter->replace($child, $newChild); + } + } + + return $filter; + } else { + /** @var Filter\Condition $filter */ + return self::transformWildcardFilter($filter); + } + } + /** * Rewrite the given filter and legacy columns * * @param Filter\Rule $filter * @param array $legacyColumns - * @param Filter\Chain|null $parent * * @return ?mixed */ - protected function rewrite(Filter\Rule $filter, array $legacyColumns, Filter\Chain $parent = null) + protected function rewrite(Filter\Rule $filter, array $legacyColumns) { $rewritten = null; if ($filter instanceof Filter\Condition) { @@ -144,53 +241,17 @@ protected function rewrite(Filter\Rule $filter, array $legacyColumns, Filter\Cha $filter->setValue($exprRule); } - $rewritten = $this->transformWildcardFilter($rewritten); - } elseif ($column === 'sort') { - $column = $filter->getValue(); - if (isset($legacyColumns[$column])) { - if ($legacyColumns[$column] === self::DROP) { - return false; - } elseif (! is_array($legacyColumns[$column])) { - return $rewritten; - } - - $column = key($legacyColumns[$column]); - - $rewritten = $filter->setValue($column); - } - - if ($parent !== null) { - foreach ($parent as $child) { - if ($child instanceof Filter\Condition && $child->getColumn() === 'dir') { - $dir = $child->getValue(); - - $rewritten = $filter->setValue("{$column} {$dir}"); - - $parent->remove($child); - } - } - } - } elseif ($column === 'dir') { - if ($parent !== null) { - foreach ($parent as $child) { - if ($child instanceof Filter\Condition && $child->getColumn() === 'sort') { - return null; - } - } - } - - return false; - } elseif (preg_match('/^_(host|service)_([\w.]+)/i', $column, $groups)) { + $rewritten = self::transformWildcardFilter($rewritten); + } elseif (preg_match('/^_(host|service)_(.+)/i', $column, $groups)) { $rewritten = $filter->setColumn($groups[1] . '.vars.' . $groups[2]); - $rewritten = $this->transformWildcardFilter($rewritten); + $rewritten = self::transformWildcardFilter($rewritten); } } else { /** @var Filter\Chain $filter */ foreach ($filter as $child) { $retVal = $this->rewrite( $child instanceof Filter\Condition ? clone $child : $child, - $legacyColumns, - $filter + $legacyColumns ); if ($retVal === false) { $filter->remove($child); @@ -203,7 +264,7 @@ protected function rewrite(Filter\Rule $filter, array $legacyColumns, Filter\Cha return $rewritten; } - private function transformWildcardFilter(Filter\Condition $filter) + private static function transformWildcardFilter(Filter\Condition $filter) { if (is_string($filter->getValue()) && strpos($filter->getValue(), '*') !== false) { if ($filter instanceof Filter\Equal) { @@ -216,6 +277,31 @@ private function transformWildcardFilter(Filter\Condition $filter) return $filter; } + protected static function commonParameters(): array + { + return [ + 'sort' => function ($value, $params, $rewriter) { + $value = $rewriter($value); + if ($params->has('dir')) { + return "{$value} {$params->get('dir')}"; + } + + return $value; + }, + 'dir' => self::DROP, + 'limit' => self::USE_EXPR, + 'showCompact' => self::USE_EXPR, + 'showFullscreen' => self::USE_EXPR, + 'view' => function ($value) { + if ($value === 'compact') { + return ['showCompact', true]; + } + + return $value; + } + ]; + } + protected static function commonColumns(): array { return [ @@ -264,14 +350,11 @@ protected static function commonColumns(): array ]; } - protected static function hostsColumns(): array + protected static function hostsParameters(): array { return [ - - // Extraordinary columns - 'addColumns' => function ($filter) { - /** @var Filter\Condition $filter */ - $legacyColumns = array_filter(array_map('trim', explode(',', $filter->getValue()))); + 'addColumns' => function ($value, $params, $rewriter) { + $legacyColumns = array_filter(array_map('trim', explode(',', $value))); $columns = [ 'host.state.soft_state', @@ -283,15 +366,20 @@ protected static function hostsColumns(): array 'host.state.is_problem' ]; foreach ($legacyColumns as $column) { - if (($c = self::transformFilter(Filter::equal($column, 'bogus'), 'hosts')) !== false) { - if ($c instanceof Filter\Condition) { - $columns[] = $c->getColumn(); - } + $column = $rewriter($column); + if ($column !== false) { + $columns[] = $column; } } - return Filter::equal('columns', implode(',', $columns)); - }, + return ['columns', implode(',', $columns)]; + } + ]; + } + + protected static function hostsColumns(): array + { + return [ // Query columns 'host_acknowledged' => [ @@ -490,14 +578,11 @@ protected static function hostColumns(): array ]; } - protected static function servicesColumns(): array + protected static function servicesParameters(): array { return [ - - // Extraordinary columns - 'addColumns' => function ($filter) { - /** @var Filter\Condition $filter */ - $legacyColumns = array_filter(array_map('trim', explode(',', $filter->getValue()))); + 'addColumns' => function ($value, $params, $rewriter) { + $legacyColumns = array_filter(array_map('trim', explode(',', $value))); $columns = [ 'service.state.soft_state', @@ -510,16 +595,20 @@ protected static function servicesColumns(): array 'service.state.is_problem' ]; foreach ($legacyColumns as $column) { - if (($c = self::transformFilter(Filter::equal($column, 'bogus'), 'services')) !== false) { - if ($c instanceof Filter\Condition) { - $columns[] = $c->getColumn(); - } + $column = $rewriter($column); + if ($column !== false) { + $columns[] = $column; } } - return Filter::equal('columns', implode(',', $columns)); - }, + return ['columns', implode(',', $columns)]; + } + ]; + } + protected static function servicesColumns(): array + { + return [ // Query columns 'host_acknowledged' => [ 'host.state.is_acknowledged' => self::NO_YES diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index baf1ffb30..262245dec 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -735,11 +735,6 @@ parameters: count: 1 path: application/controllers/MigrateController.php - - - message: "#^Parameter \\#1 \\$haystack of function strpos expects string, mixed given\\.$#" - count: 2 - path: application/controllers/MigrateController.php - - message: "#^Parameter \\#1 \\$url of static method Icinga\\\\Web\\\\Url\\:\\:fromPath\\(\\) expects string, mixed given\\.$#" count: 2 @@ -750,16 +745,6 @@ parameters: count: 3 path: application/controllers/MigrateController.php - - - message: "#^Parameter \\#2 \\$value of static method ipl\\\\Stdlib\\\\Filter\\:\\:like\\(\\) expects array\\\\|string, mixed given\\.$#" - count: 1 - path: application/controllers/MigrateController.php - - - - message: "#^Parameter \\#2 \\$value of static method ipl\\\\Stdlib\\\\Filter\\:\\:unlike\\(\\) expects array\\\\|string, mixed given\\.$#" - count: 1 - path: application/controllers/MigrateController.php - - message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Controllers\\\\NotificationsController\\:\\:completeAction\\(\\) has no return type specified\\.$#" count: 1 @@ -2735,6 +2720,11 @@ parameters: count: 1 path: library/Icingadb/Compat/UrlMigrator.php + - + message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:commonParameters\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: library/Icingadb/Compat/UrlMigrator.php + - message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:contactgroupsColumns\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -2770,6 +2760,11 @@ parameters: count: 1 path: library/Icingadb/Compat/UrlMigrator.php + - + message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:hostsParameters\\(\\) return type has no value type specified in iterable type array\\.$#" + count: 1 + path: library/Icingadb/Compat/UrlMigrator.php + - message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:multipleHostsColumns\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -2811,33 +2806,28 @@ parameters: path: library/Icingadb/Compat/UrlMigrator.php - - message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:transformWildcardFilter\\(\\) has no return type specified\\.$#" + message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:servicesParameters\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: library/Icingadb/Compat/UrlMigrator.php - - message: "#^Parameter \\#1 \\$column of method ipl\\\\Stdlib\\\\Filter\\\\Condition\\:\\:setColumn\\(\\) expects string, int\\|string\\|null given\\.$#" + message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:transformParams\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: library/Icingadb/Compat/UrlMigrator.php - - message: "#^Parameter \\#1 \\$string of function strtolower expects string, mixed given\\.$#" - count: 2 - path: library/Icingadb/Compat/UrlMigrator.php - - - - message: "#^Parameter \\#2 \\$string of function explode expects string, mixed given\\.$#" - count: 2 + message: "#^Method Icinga\\\\Module\\\\Icingadb\\\\Compat\\\\UrlMigrator\\:\\:transformWildcardFilter\\(\\) has no return type specified\\.$#" + count: 1 path: library/Icingadb/Compat/UrlMigrator.php - - message: "#^Part \\$column \\(mixed\\) of encapsed string cannot be cast to string\\.$#" + message: "#^Parameter \\#1 \\$column of method ipl\\\\Stdlib\\\\Filter\\\\Condition\\:\\:setColumn\\(\\) expects string, int\\|string\\|null given\\.$#" count: 1 path: library/Icingadb/Compat/UrlMigrator.php - - message: "#^Part \\$dir \\(mixed\\) of encapsed string cannot be cast to string\\.$#" - count: 1 + message: "#^Parameter \\#1 \\$string of function strtolower expects string, mixed given\\.$#" + count: 2 path: library/Icingadb/Compat/UrlMigrator.php -