diff --git a/eZ/Publish/API/Repository/URLWildcardService.php b/eZ/Publish/API/Repository/URLWildcardService.php index bfd84c51de..e34e674d6d 100644 --- a/eZ/Publish/API/Repository/URLWildcardService.php +++ b/eZ/Publish/API/Repository/URLWildcardService.php @@ -11,6 +11,8 @@ use eZ\Publish\API\Repository\Values\Content\URLWildcard; use eZ\Publish\API\Repository\Values\Content\URLWildcardTranslationResult; use eZ\Publish\API\Repository\Values\Content\URLWildcardUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\SearchResult; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; /** * URLAlias service. @@ -84,6 +86,12 @@ public function load(int $id): UrlWildcard; */ public function loadAll(int $offset = 0, int $limit = -1): iterable; + /** + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + */ + public function findUrlWildcards(URLWildcardQuery $query): SearchResult; + /** * Translates an url to an existing uri resource based on the * source/destination patterns of the url wildcard. diff --git a/eZ/Publish/API/Repository/Values/Content/Query/Criterion/LogicalOperator.php b/eZ/Publish/API/Repository/Values/Content/Query/Criterion/LogicalOperator.php index 2157f931d2..9738056d1c 100644 --- a/eZ/Publish/API/Repository/Values/Content/Query/Criterion/LogicalOperator.php +++ b/eZ/Publish/API/Repository/Values/Content/Query/Criterion/LogicalOperator.php @@ -10,7 +10,7 @@ use eZ\Publish\API\Repository\Exceptions\NotImplementedException; use eZ\Publish\API\Repository\Values\Content\Query\Criterion; -use InvalidArgumentException; +use Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException; /** * Note that the class should ideally have been in a Logical namespace, but it would have then be named 'And', @@ -30,26 +30,15 @@ abstract class LogicalOperator extends Criterion * * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion[] $criteria * - * @throws \InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException */ public function __construct(array $criteria) { foreach ($criteria as $key => $criterion) { if (!$criterion instanceof Criterion) { - if ($criterion === null) { - $type = 'null'; - } elseif (is_object($criterion)) { - $type = get_class($criterion); - } elseif (is_array($criterion)) { - $type = 'Array, with keys: ' . implode(', ', array_keys($criterion)); - } else { - $type = gettype($criterion) . ", with value: '{$criterion}'"; - } - - throw new InvalidArgumentException( - "You provided {$type} at index '{$key}', but only Criterion objects are accepted" - ); + throw new InvalidCriterionArgumentException($key, $criterion, Criterion::class); } + $this->criteria[] = $criterion; } } diff --git a/eZ/Publish/API/Repository/Values/URL/Query/Criterion/LogicalOperator.php b/eZ/Publish/API/Repository/Values/URL/Query/Criterion/LogicalOperator.php index 5b082861d8..17faa70f8a 100644 --- a/eZ/Publish/API/Repository/Values/URL/Query/Criterion/LogicalOperator.php +++ b/eZ/Publish/API/Repository/Values/URL/Query/Criterion/LogicalOperator.php @@ -9,7 +9,7 @@ namespace eZ\Publish\API\Repository\Values\URL\Query\Criterion; use eZ\Publish\API\Repository\Values\URL\Query\Criterion; -use InvalidArgumentException; +use Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException; abstract class LogicalOperator extends Criterion { @@ -25,25 +25,13 @@ abstract class LogicalOperator extends Criterion * * @param \eZ\Publish\API\Repository\Values\URL\Query\Criterion[] $criteria * - * @throws \InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException */ public function __construct(array $criteria) { foreach ($criteria as $key => $criterion) { if (!$criterion instanceof Criterion) { - if ($criterion === null) { - $type = 'null'; - } elseif (is_object($criterion)) { - $type = get_class($criterion); - } elseif (is_array($criterion)) { - $type = 'Array, with keys: ' . implode(', ', array_keys($criterion)); - } else { - $type = gettype($criterion) . ", with value: '{$criterion}'"; - } - - throw new InvalidArgumentException( - "You provided {$type} at index '{$key}', but only Criterion objects are accepted" - ); + throw new InvalidCriterionArgumentException($key, $criterion, Criterion::class); } $this->criteria[] = $criterion; diff --git a/eZ/Publish/Core/Persistence/Cache/UrlWildcardHandler.php b/eZ/Publish/Core/Persistence/Cache/UrlWildcardHandler.php index a629002431..b0eed0b8d8 100644 --- a/eZ/Publish/Core/Persistence/Cache/UrlWildcardHandler.php +++ b/eZ/Publish/Core/Persistence/Cache/UrlWildcardHandler.php @@ -10,6 +10,7 @@ use eZ\Publish\Core\Base\Exceptions\NotFoundException; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; use eZ\Publish\SPI\Persistence\Content\UrlWildcard\Handler as UrlWildcardHandlerInterface; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; class UrlWildcardHandler extends AbstractHandler implements UrlWildcardHandlerInterface { @@ -132,6 +133,16 @@ public function loadAll($offset = 0, $limit = -1) return $this->persistenceHandler->urlWildcardHandler()->loadAll($offset, $limit); } + /** + * @see \eZ\Publish\SPI\Persistence\Content\UrlWildcard\Handler::find + */ + public function find(URLWildcardQuery $query): array + { + $this->logger->logCall(__METHOD__, ['query' => $query]); + + return $this->persistenceHandler->urlWildcardHandler()->find($query); + } + /** * @see \eZ\Publish\SPI\Persistence\Content\UrlWildcard\Handler::lookup */ diff --git a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway.php b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway.php index c2ce6e3a48..50924d84e3 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway.php +++ b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway.php @@ -9,6 +9,7 @@ namespace eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\Criterion; /** * UrlWildcard Gateway. @@ -50,6 +51,25 @@ abstract public function loadUrlWildcardData(int $id): array; */ abstract public function loadUrlWildcardsData(int $offset = 0, int $limit = -1): array; + /** + * Selects URLWildcards matching specified criteria. + * + * @return array{ + * "rows": mixed, + * "count": int|null, + * } + * + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + * @throws \eZ\Publish\API\Repository\Exceptions\NotImplementedException if Criterion is not applicable to its target + */ + abstract public function find( + Criterion $criterion, + int $offset, + int $limit, + array $sortClauses = [], + bool $doCount = true + ): array; + /** * Load the UrlWildcard by source url $sourceUrl. */ diff --git a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/DoctrineDatabase.php b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/DoctrineDatabase.php index 1a29bae980..c38c4e869d 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/DoctrineDatabase.php +++ b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/DoctrineDatabase.php @@ -12,8 +12,13 @@ use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; +use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; use eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\Criterion; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\SortClause; +use Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter; +use RuntimeException; /** * URL wildcard gateway implementation using the Doctrine database. @@ -33,9 +38,18 @@ final class DoctrineDatabase extends Gateway /** @var \Doctrine\DBAL\Connection */ private $connection; - public function __construct(Connection $connection) + /** @var \Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter */ + protected $criteriaConverter; + + public const SORT_DIRECTION_MAP = [ + SortClause::SORT_ASC => 'ASC', + SortClause::SORT_DESC => 'DESC', + ]; + + public function __construct(Connection $connection, CriteriaConverter $criteriaConverter) { $this->connection = $connection; + $this->criteriaConverter = $criteriaConverter; } public function insertUrlWildcard(UrlWildcard $urlWildcard): int @@ -159,6 +173,47 @@ public function loadUrlWildcardsData(int $offset = 0, int $limit = -1): array return $stmt->fetchAll(FetchMode::ASSOCIATIVE); } + public function find( + Criterion $criterion, + int $offset, + int $limit, + array $sortClauses = [], + bool $doCount = true + ): array { + $count = $doCount ? $this->doCount($criterion) : null; + if (!$doCount && $limit === 0) { + throw new RuntimeException('Invalid query. Cannot disable count and request 0 items at the same time'); + } + + if ($limit === 0 || ($count !== null && $count <= $offset)) { + return [ + 'count' => $count, + 'rows' => [], + ]; + } + + if ($limit < 0) { + throw new InvalidArgumentException('$limit', 'The limit need be higher than 0'); + } + + $query = $this->buildLoadUrlWildcardDataQuery(); + $query + ->where($this->criteriaConverter->convertCriteria($query, $criterion)) + ->setMaxResults($limit) + ->setFirstResult($offset); + + foreach ($sortClauses as $sortClause) { + $query->addOrderBy($sortClause->target, $this->getQuerySortingDirection($sortClause->direction)); + } + + $statement = $query->execute(); + + return [ + 'count' => $count, + 'rows' => $statement->fetchAllAssociative(), + ]; + } + public function loadUrlWildcardBySourceUrl(string $sourceUrl): array { $query = $this->buildLoadUrlWildcardDataQuery(); @@ -186,6 +241,42 @@ public function countAll(): int return (int) $query->execute()->fetchColumn(); } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\Criterion $criterion + * + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \Doctrine\DBAL\Exception + * @throws \eZ\Publish\API\Repository\Exceptions\NotImplementedException + */ + protected function doCount(Criterion $criterion): int + { + $query = $this->connection->createQueryBuilder(); + $query + ->select($this->connection->getDatabasePlatform()->getCountExpression('url_wildcard.id')) + ->from(self::URL_WILDCARD_TABLE, 'url_wildcard') + ->where($this->criteriaConverter->convertCriteria($query, $criterion)); + + return (int)$query->execute()->fetchOne(); + } + + /** + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + */ + private function getQuerySortingDirection(string $direction): string + { + if (!isset(self::SORT_DIRECTION_MAP[$direction])) { + throw new InvalidArgumentException( + '$sortClause->direction', + sprintf( + 'Unsupported "%s" sorting direction, use one of the SortClause::SORT_* constants instead', + $direction + ) + ); + } + + return self::SORT_DIRECTION_MAP[$direction]; + } + private function trimUrl(string $url): string { return trim($url, '/'); diff --git a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/ExceptionConversion.php b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/ExceptionConversion.php index 0e974cea76..c0810eb309 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/ExceptionConversion.php +++ b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Gateway/ExceptionConversion.php @@ -12,6 +12,7 @@ use eZ\Publish\Core\Base\Exceptions\DatabaseException; use eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\Criterion; use PDOException; /** @@ -90,6 +91,20 @@ public function loadUrlWildcardsData(int $offset = 0, int $limit = -1): array } } + public function find( + Criterion $criterion, + int $offset, + int $limit, + array $sortClauses = [], + bool $doCount = true + ): array { + try { + return $this->innerGateway->find($criterion, $offset, $limit, $sortClauses, $doCount); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + public function loadUrlWildcardBySourceUrl(string $sourceUrl): array { try { diff --git a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Handler.php b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Handler.php index 998f43e21f..f3b9e8c703 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Handler.php +++ b/eZ/Publish/Core/Persistence/Legacy/Content/UrlWildcard/Handler.php @@ -9,6 +9,7 @@ use eZ\Publish\Core\Base\Exceptions\NotFoundException; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; use eZ\Publish\SPI\Persistence\Content\UrlWildcard\Handler as BaseUrlWildcardHandler; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; /** * The UrlWildcard Handler provides nice urls with wildcards management. @@ -139,6 +140,22 @@ public function loadAll($offset = 0, $limit = -1) ); } + public function find(URLWildcardQuery $query): array + { + $results = $this->gateway->find( + $query->filter, + $query->offset, + $query->limit, + $query->sortClauses, + $query->performCount + ); + + return [ + 'count' => $results['count'], + 'items' => $this->mapper->extractUrlWildcardsFromRows($results['rows']), + ]; + } + /** * Performs lookup for given URL. * diff --git a/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/Gateway/DoctrineDatabaseTest.php b/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/Gateway/DoctrineDatabaseTest.php index 55a6a2b068..f51af8d1e2 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/Gateway/DoctrineDatabaseTest.php +++ b/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/Gateway/DoctrineDatabaseTest.php @@ -11,6 +11,8 @@ use eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway\DoctrineDatabase; use eZ\Publish\Core\Persistence\Legacy\Tests\TestCase; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter; +use Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriterionHandler\MatchAll; /** * Test case for eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway\DoctrineDatabase. @@ -178,7 +180,8 @@ public function testDeleteUrlWildcard() protected function getGateway(): DoctrineDatabase { if (!isset($this->gateway)) { - $this->gateway = new DoctrineDatabase($this->getDatabaseConnection()); + $criteriaConverter = new CriteriaConverter([new MatchAll()]); + $this->gateway = new DoctrineDatabase($this->getDatabaseConnection(), $criteriaConverter); } return $this->gateway; diff --git a/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/UrlWildcardHandlerTest.php b/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/UrlWildcardHandlerTest.php index 0c53082628..abc3981fa2 100644 --- a/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/UrlWildcardHandlerTest.php +++ b/eZ/Publish/Core/Persistence/Legacy/Tests/Content/UrlWildcard/UrlWildcardHandlerTest.php @@ -13,6 +13,8 @@ use eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Mapper; use eZ\Publish\Core\Persistence\Legacy\Tests\TestCase; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter; +use Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriterionHandler\MatchAll; /** * Test case for UrlWildcard Handler. @@ -245,7 +247,8 @@ public function testLoadAllWithOffsetAndLimit() protected function getHandler(): UrlWildcard\Handler { if (!isset($this->urlWildcardHandler)) { - $this->gateway = new DoctrineDatabase($this->getDatabaseConnection()); + $criteriaConverter = new CriteriaConverter([new MatchAll()]); + $this->gateway = new DoctrineDatabase($this->getDatabaseConnection(), $criteriaConverter); $this->mapper = new Mapper(); $this->urlWildcardHandler = new Handler( diff --git a/eZ/Publish/Core/Repository/URLWildcardService.php b/eZ/Publish/Core/Repository/URLWildcardService.php index f5a51ffd69..d22a423dcf 100644 --- a/eZ/Publish/Core/Repository/URLWildcardService.php +++ b/eZ/Publish/Core/Repository/URLWildcardService.php @@ -17,9 +17,12 @@ use eZ\Publish\API\Repository\Values\Content\URLWildcardUpdateStruct; use eZ\Publish\Core\Base\Exceptions\ContentValidationException; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; +use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue; use eZ\Publish\Core\Base\Exceptions\UnauthorizedException; use eZ\Publish\SPI\Persistence\Content\UrlWildcard as SPIUrlWildcard; use eZ\Publish\SPI\Persistence\Content\UrlWildcard\Handler; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\SearchResult; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; /** * URLAlias service. @@ -207,9 +210,29 @@ public function loadAll(int $offset = 0, int $limit = -1): iterable return $urlWildcards; } - /** - * {@inheritDoc} - */ + public function findUrlWildcards(URLWildcardQuery $query): SearchResult + { + if ($query->offset !== null && !is_numeric($query->offset)) { + throw new InvalidArgumentValue('offset', $query->offset); + } + + if ($query->limit !== null && !is_numeric($query->limit)) { + throw new InvalidArgumentValue('limit', $query->limit); + } + + $results = $this->urlWildcardHandler->find($query); + + $items = []; + foreach ($results['items'] as $urlWildcard) { + $items[] = $this->buildUrlWildcardDomainObject($urlWildcard); + } + + return new SearchResult([ + 'totalCount' => $results['count'], + 'items' => $items, + ]); + } + public function countAll(): int { return $this->urlWildcardHandler->countAll(); diff --git a/eZ/Publish/Core/settings/storage_engines/legacy.yml b/eZ/Publish/Core/settings/storage_engines/legacy.yml index 046b27d8ef..81c0cd3540 100644 --- a/eZ/Publish/Core/settings/storage_engines/legacy.yml +++ b/eZ/Publish/Core/settings/storage_engines/legacy.yml @@ -15,6 +15,7 @@ imports: - {resource: storage_engines/legacy/url_wildcard.yml} - {resource: storage_engines/legacy/url.yml} - {resource: storage_engines/legacy/url_criterion_handlers.yml} + - {resource: storage_engines/legacy/url_wildcard_criterion_handlers.yml} - {resource: storage_engines/legacy/user.yml} - {resource: storage_engines/legacy/notification.yml} - {resource: storage_engines/legacy/user_preference.yml} diff --git a/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard.yml b/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard.yml index 4696ad8a2c..b7b0a219e7 100644 --- a/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard.yml +++ b/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard.yml @@ -3,6 +3,7 @@ services: class: eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway\DoctrineDatabase arguments: - '@ezpublish.api.storage_engine.legacy.connection' + - '@Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter' ezpublish.persistence.legacy.url_wildcard.gateway.exception_conversion: class: eZ\Publish\Core\Persistence\Legacy\Content\UrlWildcard\Gateway\ExceptionConversion @@ -22,3 +23,7 @@ services: - "@ezpublish.persistence.legacy.url_wildcard.gateway" - "@ezpublish.persistence.legacy.url_wildcard.mapper" lazy: true + + Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriteriaConverter: + arguments: + $handlers: !tagged_iterator ibexa.storage.legacy.url_wildcard.criterion.handler diff --git a/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard_criterion_handlers.yml b/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard_criterion_handlers.yml new file mode 100644 index 0000000000..7b07d2411d --- /dev/null +++ b/eZ/Publish/Core/settings/storage_engines/legacy/url_wildcard_criterion_handlers.yml @@ -0,0 +1,5 @@ +services: + Ibexa\Core\Persistence\Legacy\Content\URLWildcard\Query\CriterionHandler\: + resource: '../../../../../../src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/*' + tags: + - { name: ibexa.storage.legacy.url_wildcard.criterion.handler } diff --git a/eZ/Publish/SPI/Persistence/Content/UrlWildcard/Handler.php b/eZ/Publish/SPI/Persistence/Content/UrlWildcard/Handler.php index 9d5ac5f863..8e24842a60 100644 --- a/eZ/Publish/SPI/Persistence/Content/UrlWildcard/Handler.php +++ b/eZ/Publish/SPI/Persistence/Content/UrlWildcard/Handler.php @@ -7,6 +7,7 @@ namespace eZ\Publish\SPI\Persistence\Content\UrlWildcard; use eZ\Publish\SPI\Persistence\Content\UrlWildcard; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; /** * The UrlWildcard Handler interface provides nice urls with wildcards management. @@ -64,6 +65,13 @@ public function load($id); */ public function loadAll($offset = 0, $limit = -1); + /** + * Find URLWildcards. + * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function find(URLWildcardQuery $query): array; + /** * Performs lookup for given (source) URL. * diff --git a/eZ/Publish/SPI/Repository/Decorator/URLWildcardServiceDecorator.php b/eZ/Publish/SPI/Repository/Decorator/URLWildcardServiceDecorator.php index d05e7d0736..912c296d0f 100644 --- a/eZ/Publish/SPI/Repository/Decorator/URLWildcardServiceDecorator.php +++ b/eZ/Publish/SPI/Repository/Decorator/URLWildcardServiceDecorator.php @@ -12,6 +12,8 @@ use eZ\Publish\API\Repository\Values\Content\URLWildcard; use eZ\Publish\API\Repository\Values\Content\URLWildcardTranslationResult; use eZ\Publish\API\Repository\Values\Content\URLWildcardUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\SearchResult; +use Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\URLWildcardQuery; abstract class URLWildcardServiceDecorator implements URLWildcardService { @@ -55,6 +57,11 @@ public function loadAll( return $this->innerService->loadAll($offset, $limit); } + public function findUrlWildcards(URLWildcardQuery $query): SearchResult + { + return $this->innerService->findUrlWildcards($query); + } + public function translate(string $url): URLWildcardTranslationResult { return $this->innerService->translate($url); diff --git a/src/contracts/Repository/Exceptions/InvalidCriterionArgumentException.php b/src/contracts/Repository/Exceptions/InvalidCriterionArgumentException.php new file mode 100644 index 0000000000..0ffa6a1483 --- /dev/null +++ b/src/contracts/Repository/Exceptions/InvalidCriterionArgumentException.php @@ -0,0 +1,29 @@ +destinationUrl = $destinationUrl; + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalAnd.php b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalAnd.php new file mode 100644 index 0000000000..89724577b3 --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalAnd.php @@ -0,0 +1,13 @@ + $criterion) { + if (!$criterion instanceof Criterion) { + throw new InvalidCriterionArgumentException($key, $criterion, Criterion::class); + } + + $this->criteria[] = $criterion; + } + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalOr.php b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalOr.php new file mode 100644 index 0000000000..f1c16afaca --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/LogicalOr.php @@ -0,0 +1,13 @@ +sourceUrl = $sourceUrl; + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/Type.php b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/Type.php new file mode 100644 index 0000000000..fbfcae9143 --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/Query/Criterion/Type.php @@ -0,0 +1,23 @@ +forward = $forward; + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause.php b/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause.php new file mode 100644 index 0000000000..0ad4507218 --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause.php @@ -0,0 +1,44 @@ +direction = $sortDirection; + $this->target = $sortTarget; + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause/DestinationUrl.php b/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause/DestinationUrl.php new file mode 100644 index 0000000000..b7cac3c205 --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/Query/SortClause/DestinationUrl.php @@ -0,0 +1,19 @@ +items); + } +} diff --git a/src/contracts/Repository/Values/Content/URLWildcard/URLWildcardQuery.php b/src/contracts/Repository/Values/Content/URLWildcard/URLWildcardQuery.php new file mode 100644 index 0000000000..61ce4fac94 --- /dev/null +++ b/src/contracts/Repository/Values/Content/URLWildcard/URLWildcardQuery.php @@ -0,0 +1,57 @@ +handlers = $handlers; + } + + /** + * @throws \eZ\Publish\API\Repository\Exceptions\NotImplementedException if Criterion is not applicable to its target + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|string + */ + public function convertCriteria(QueryBuilder $queryBuilder, Criterion $criterion) + { + foreach ($this->handlers as $handler) { + if ($handler->accept($criterion)) { + return $handler->handle($this, $queryBuilder, $criterion); + } + } + + throw new NotImplementedException( + 'No visitor available for: ' . get_class($criterion) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler.php new file mode 100644 index 0000000000..013fb9ba89 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler.php @@ -0,0 +1,33 @@ +expr()->like( + 'destination_url', + $queryBuilder->createNamedParameter( + '%' . $criterion->destinationUrl . '%', + ParameterType::STRING, + ':destination_url' + ) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalAnd.php new file mode 100644 index 0000000000..be4cf0fce0 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalAnd.php @@ -0,0 +1,38 @@ +criteria as $subCriterion) { + $subexpressions[] = $converter->convertCriteria($queryBuilder, $subCriterion); + } + + return $queryBuilder->expr()->and(...$subexpressions); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalNot.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalNot.php new file mode 100644 index 0000000000..a022740fd3 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalNot.php @@ -0,0 +1,38 @@ +convertCriteria($queryBuilder, $criterion->criteria[0]) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalOr.php new file mode 100644 index 0000000000..89917fdc32 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/LogicalOr.php @@ -0,0 +1,41 @@ +criteria as $subCriterion) { + $subexpressions[] = $converter->convertCriteria($queryBuilder, $subCriterion); + } + + return $queryBuilder->expr()->or(...$subexpressions); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/MatchAll.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/MatchAll.php new file mode 100644 index 0000000000..748ff2680c --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/MatchAll.php @@ -0,0 +1,33 @@ +expr()->like( + 'source_url', + $queryBuilder->createNamedParameter( + '%' . $criterion->sourceUrl . '%', + ParameterType::STRING, + ':source_url' + ) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/Type.php b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/Type.php new file mode 100644 index 0000000000..32e103dc57 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/URLWildcard/Query/CriterionHandler/Type.php @@ -0,0 +1,42 @@ +expr()->eq( + 'type', + $queryBuilder->createNamedParameter( + $criterion->forward ? self::FORWARD : self::DIRECT, + ParameterType::INTEGER, + ':type' + ) + ); + } +} diff --git a/tests/integration/Core/Repository/URLWildcardService/CriterionTest.php b/tests/integration/Core/Repository/URLWildcardService/CriterionTest.php new file mode 100644 index 0000000000..0b194d5031 --- /dev/null +++ b/tests/integration/Core/Repository/URLWildcardService/CriterionTest.php @@ -0,0 +1,292 @@ +getRepository(); + $urlWildcardService = $repository->getURLWildcardService(); + + foreach ($this->getUrlWildcards() as $urlWildcard) { + $urlWildcardService->create($urlWildcard['sourceUrl'], $urlWildcard['destinationUrl'], $urlWildcard['forward']); + } + } + + protected function findUrlWildcards( + URLWildcardQuery $query, + ?int $expectedTotalCount + ): SearchResult { + $repository = $this->getRepository(); + $searchResult = $repository->getURLWildcardService()->findUrlWildcards($query); + + self::assertSame($expectedTotalCount, $searchResult->totalCount); + self::assertCount($expectedTotalCount, $searchResult->items); + + return $searchResult; + } + + private function getUrlWildcards(bool $isAbsolute = false): array + { + $prefix = $isAbsolute ? '/' : ''; + + return [ + [ + 'sourceUrl' => $prefix . 'test', + 'destinationUrl' => $prefix . 'content-test', + 'forward' => true, + ], + [ + 'sourceUrl' => $prefix . 'test test', + 'destinationUrl' => $prefix . 'content test', + 'forward' => true, + ], + [ + 'sourceUrl' => $prefix . 'ibexa-dxp', + 'destinationUrl' => $prefix . 'ibexa-1-2-3', + 'forward' => true, + ], + [ + 'sourceUrl' => $prefix . 'nice-url-seo', + 'destinationUrl' => $prefix . '1/2/3/4', + 'forward' => false, + ], + [ + 'sourceUrl' => $prefix . 'no-forward test url', + 'destinationUrl' => $prefix . 'no/forward test url', + 'forward' => false, + ], + [ + 'sourceUrl' => $prefix . 'Twitter', + 'destinationUrl' => $prefix . 'a/b/c', + 'forward' => false, + ], + [ + 'sourceUrl' => $prefix . 'facebook', + 'destinationUrl' => $prefix . '2/3/facebook', + 'forward' => true, + ], + ]; + } + + public function testMatchAll(): void + { + $query = new URLWildcardQuery(); + $query->filter = new Criterion\MatchAll(); + + $expectedWildcardUrls = $this->getUrlWildcards(true); + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + + foreach ($searchResult->items as $item) { + $wildcard = [ + 'sourceUrl' => $item->sourceUrl, + 'destinationUrl' => $item->destinationUrl, + 'forward' => $item->forward, + ]; + + self::assertContains($wildcard, $expectedWildcardUrls); + } + } + + public function testMatchNone(): void + { + $query = new URLWildcardQuery(); + $query->filter = new Criterion\MatchNone(); + + $this->findUrlWildcards($query, 0); + } + + public function testSourceUrl(): void + { + $expectedWildcardUrls = [ + '/test', + '/test test', + '/no-forward test url', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\SourceUrl('test'); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls); + } + + public function testSourceUrlWithSpace(): void + { + $expectedWildcardUrls = [ + '/test test', + '/no-forward test url', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\SourceUrl(' test'); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls); + } + + public function testDestinationUrl(): void + { + $expectedWildcardUrls = [ + '/content-test', + '/content test', + '/no/forward test url', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\DestinationUrl('test'); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls, false); + } + + public function testDestinationUrlWithSpace(): void + { + $expectedWildcardUrls = [ + '/content test', + '/no/forward test url', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\DestinationUrl(' test'); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls, false); + } + + public function testTypeForward(): void + { + $expectedWildcardUrls = [ + '/test', + '/test test', + '/ibexa-dxp', + '/facebook', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\Type(true); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls); + } + + public function testTypeNoForward(): void + { + $expectedWildcardUrls = [ + '/nice-url-seo', + '/no-forward test url', + '/Twitter', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\Type(false); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrls)); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrls); + } + + public function testInvalidLimitThrowsInvalidArgumentException(): void + { + $query = new URLWildcardQuery(); + $query->filter = new Criterion\MatchAll(); + $query->limit = 'invalid!'; + + $repository = $this->getRepository(); + $urlWildcardService = $repository->getURLWildcardService(); + + $this->expectException(InvalidArgumentValue::class); + $urlWildcardService->findUrlWildcards($query); + } + + public function testInvalidOffsetThrowsInvalidArgumentException(): void + { + $query = new URLWildcardQuery(); + $query->filter = new Criterion\MatchAll(); + $query->offset = 'invalid!'; + + $repository = $this->getRepository(); + $urlWildcardService = $repository->getURLWildcardService(); + + $this->expectException(InvalidArgumentValue::class); + $urlWildcardService->findUrlWildcards($query); + } + + public function testSourceAndDestination(): void + { + $search = 'test'; + $expectedWildcardUrlsSource = [ + '/test', + '/test test', + '/no-forward test url', + ]; + + $expectedWildcardUrlsDestination = [ + '/content-test', + '/content test', + '/no/forward test url', + ]; + + $query = new URLWildcardQuery(); + $query->filter = new Criterion\LogicalAnd([ + new Criterion\SourceUrl($search), + new Criterion\DestinationUrl($search), + ]); + + $searchResult = $this->findUrlWildcards($query, count($expectedWildcardUrlsSource)); + + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrlsSource); + $this->checkWildcardUrl($searchResult->items, $expectedWildcardUrlsDestination, false); + } + + public function testLogicalInvalidCriterion(): void + { + $this->expectException(InvalidCriterionArgumentException::class); + $this->expectExceptionMessage( + 'You provided eZ\Publish\API\Repository\Values\URL\Query\Criterion\VisibleOnly ' . + "at index '1', but only instances of " . + "'Ibexa\Contracts\Core\Repository\Values\Content\URLWildcard\Query\Criterion' are accepted" + ); + $query = new URLWildcardQuery(); + $query->filter = new Criterion\LogicalAnd([ + new Criterion\SourceUrl('test'), + new CriterionURL\VisibleOnly(), + ]); + } + + /** + * @param \eZ\Publish\API\Repository\Values\Content\URLWildcard[] $items + * @param string[] $expectedWildcardUrls + */ + protected function checkWildcardUrl(array $items, array $expectedWildcardUrls, bool $sourceUrl = true): void + { + foreach ($items as $item) { + if ($sourceUrl) { + self::assertContains($item->sourceUrl, $expectedWildcardUrls); + } else { + self::assertContains($item->destinationUrl, $expectedWildcardUrls); + } + } + } +}