Skip to content

Commit

Permalink
EZP-31107: Added build-in location based Query Types
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwojs committed Feb 27, 2020
1 parent ce46385 commit 3247334
Show file tree
Hide file tree
Showing 19 changed files with 1,768 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ private function handleApiLoading(ContainerBuilder $container, FileLoader $loade

// Storage engine
$loader->load('storage_engines.yml');

// Build-in query types
$loader->load('query_types.yml');
}

/**
Expand Down
29 changes: 29 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/query_types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

eZ\Publish\Core\QueryType\BuildIn\AncestorsQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:Ancestors' }

eZ\Publish\Core\QueryType\BuildIn\ChildrenQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:Children' }

eZ\Publish\Core\QueryType\BuildIn\SiblingsQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:Siblings' }

eZ\Publish\Core\QueryType\BuildIn\RelatedToContentQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:RelatedTo' }

eZ\Publish\Core\QueryType\BuildIn\GeoLocationQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:GeoLocation' }

eZ\Publish\Core\QueryType\BuildIn\SubtreeQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:Subtree' }
71 changes: 71 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/AbstractLocationQueryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

use eZ\Publish\API\Repository\Values\Content\Content;
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
use eZ\Publish\API\Repository\Values\Content\Location;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

abstract class AbstractLocationQueryType extends AbstractQueryType
{
protected function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);

$resolver->setDefaults([
'location' => null,
'content' => null,
]);

$resolver->setAllowedTypes('location', ['null', 'int', Location::class]);
$resolver->setNormalizer(
'location',
function (Options $options, $value): ?Location {
if (is_int($value)) {
return $this->repository->getLocationService()->loadLocation($value);
}

return $value;
}
);

$resolver->setAllowedTypes('content', ['null', 'int', Content::class, ContentInfo::class]);
$resolver->setNormalizer(
'content',
function (Options $options, $value): ?ContentInfo {
if (is_int($value)) {
return $this->repository->getContentService()->loadContentInfo($value);
}

if ($value instanceof Content) {
return $value->contentInfo;
}

return $value;
}
);
}

protected function resolveLocation(array $parameters): ?Location
{
$location = $parameters['location'];

if ($location === null) {
$content = $parameters['content'];

if ($content instanceof ContentInfo) {
$location = $content->getMainLocation();
}
}

return $location;
}
}
147 changes: 147 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/AbstractQueryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

use eZ\Publish\API\Repository\Repository;
use eZ\Publish\API\Repository\Values\Content\Query;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Visibility;
use eZ\Publish\Core\MVC\ConfigResolverInterface;
use eZ\Publish\Core\QueryType\OptionsResolverBasedQueryType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

abstract class AbstractQueryType extends OptionsResolverBasedQueryType
{
private const SORT_CLAUSE_NAMESPACE = '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\\';

public const DEFAULT_LIMIT = 25;

/** @var \eZ\Publish\API\Repository\Repository */
protected $repository;

/** @var \eZ\Publish\Core\MVC\ConfigResolverInterface */
protected $configResolver;

public function __construct(Repository $repository, ConfigResolverInterface $configResolver)
{
$this->repository = $repository;
$this->configResolver = $configResolver;
}

protected function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'filter' => static function (OptionsResolver $resolver): void {
$resolver->setDefaults([
'content_type' => [],
'visible_only' => true,
'siteaccess_aware' => true,
]);

$resolver->setAllowedTypes('content_type', 'array');
$resolver->setAllowedTypes('visible_only', 'bool');
$resolver->setAllowedTypes('siteaccess_aware', 'bool');
},
'offset' => 0,
'limit' => self::DEFAULT_LIMIT,
'sort' => static function (OptionsResolver $resolver): void {
$resolver->setDefault('target', null);
$resolver->setAllowedTypes('target', ['null', 'string']);

$resolver->setDefault('direction', 'asc');
$resolver->setAllowedValues('direction', ['asc', 'desc']);
$resolver->setNormalizer('direction', function (Options $options, $direction) {
if (strtolower($direction) === 'desc') {
return Query::SORT_DESC;
}

return Query::SORT_ASC;
});

$resolver->setDefault('data', null);
},
]);

$resolver->setAllowedTypes('offset', 'int');
$resolver->setAllowedTypes('limit', 'int');
}

abstract protected function getQueryFilter(array $parameters): Criterion;

protected function doGetQuery(array $parameters): Query
{
$query = new Query();
$query->filter = $this->buildFilters($parameters);

if ($parameters['sort']['target'] !== null) {
$query->sortClauses = $this->buildSortClauses(
$parameters['sort']['target'],
$parameters['sort']['direction'],
$parameters['sort']['data'] ?? []
);
}

$query->limit = $parameters['limit'];
$query->offset = $parameters['offset'];

return $query;
}

private function buildFilters(array $parameters): Criterion
{
$criteria = [
$this->getQueryFilter($parameters),
];

if ($parameters['filter']['visible_only']) {
$criteria[] = new Visibility(Visibility::VISIBLE);
}

if (!empty($parameters['filter']['content_type'])) {
$criteria[] = new ContentTypeIdentifier($parameters['filter']['content_type']);
}

if ($parameters['filter']['siteaccess_aware']) {
// Limit results to current SiteAccess tree root
$criteria[] = new Subtree($this->getRootLocationPathString());
}

return new LogicalAnd($criteria);
}

private function buildSortClauses(string $class, ?string $direction, array $args): array
{
if (substr($class, 0, 1) !== '\\') {
$class = self::SORT_CLAUSE_NAMESPACE . $class;
}

if (class_exists($class)) {
/** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause $sortClause */
$sortClause = new $class(...$args);
$sortClause->direction = $direction;

return [$sortClause];
}

return [];
}

private function getRootLocationPathString(): string
{
$rootLocation = $this->repository->getLocationService()->loadLocation(
$this->configResolver->getParameter('content.tree_root.location_id')
);

return $rootLocation->pathString;
}
}
40 changes: 40 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/AncestorsQueryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Ancestor;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LocationId;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalNot;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchNone;

final class AncestorsQueryType extends AbstractLocationQueryType
{
public static function getName(): string
{
return 'eZ:Ancestors';
}

protected function getQueryFilter(array $parameters): Criterion
{
$location = $this->resolveLocation($parameters);

if ($location === null) {
return new MatchNone();
}

return new LogicalAnd([
new Ancestor($location->pathString),
new LogicalNot(
new LocationId($location->id)
),
]);
}
}
32 changes: 32 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/ChildrenQueryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\MatchNone;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId;

final class ChildrenQueryType extends AbstractLocationQueryType
{
public static function getName(): string
{
return 'eZ:Children';
}

protected function getQueryFilter(array $parameters): Criterion
{
$location = $this->resolveLocation($parameters);

if ($location === null) {
return new MatchNone();
}

return new ParentLocationId($location->id);
}
}
Loading

0 comments on commit 3247334

Please sign in to comment.