Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EZP-31107: Built-in query types for site building #2938

Merged
merged 6 commits into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');

$loader->load('query_types.yml');
$loader->load('sort_spec.yml');
}

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

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

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

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

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

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

eZ\Publish\Core\QueryType\BuiltIn\SubtreeQueryType:
tags:
- { name: ezpublish.query_type, alias: 'Subtree' }

eZ\Publish\Core\QueryType\BuiltIn\SortClausesFactory:
arguments:
$sortClauseArgsParser: '@eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParserDispatcher'

eZ\Publish\Core\QueryType\BuiltIn\SortClausesFactoryInterface:
alias: 'eZ\Publish\Core\QueryType\BuiltIn\SortClausesFactory'
39 changes: 39 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/sort_spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParserDispatcher:
arguments:
$parsers: !tagged_iterator ezplatform.query_type.sort_clause_parser

eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParser\FieldSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParser\MapDistanceSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParser\RandomSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuiltIn\SortSpec\SortClauseParser\DefaultSortClauseParser:
arguments:
$valueObjectClassMap:
content_id: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId
adamwojs marked this conversation as resolved.
Show resolved Hide resolved
content_name: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName
date_modified: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified
date_published: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished
section_identifier: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier
section_name: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName
location_depth: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Depth
location_id: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Id
location_is_main: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\IsMainLocation
location_path: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Path
location_priority: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Priority
location_visibility: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Visibility
alongosz marked this conversation as resolved.
Show resolved Hide resolved
tags:
- { name: ezplatform.query_type.sort_clause_parser }
71 changes: 71 additions & 0 deletions eZ/Publish/Core/QueryType/BuiltIn/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\BuiltIn;

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;
}
}
130 changes: 130 additions & 0 deletions eZ/Publish/Core/QueryType/BuiltIn/AbstractQueryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?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\BuiltIn;

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\API\Repository\Values\Content\Query\SortClause;
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
{
public const DEFAULT_LIMIT = 25;

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

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

/** @var \eZ\Publish\Core\QueryType\BuiltIn\SortClausesFactoryInterface */
private $sortClausesFactory;

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

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' => [],
]);

$resolver->setNormalizer('sort', function (Options $options, $value) {
if (is_string($value)) {
$value = $this->sortClausesFactory->createFromSpecification($value);
}

if (!is_array($value)) {
$value = [$value];
}

return $value;
});

$resolver->setAllowedTypes('sort', ['string', 'array', SortClause::class]);
$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'] !== null) {
$query->sortClauses = $parameters['sort'];
}

$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 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/BuiltIn/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\BuiltIn;

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 '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/BuiltIn/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\BuiltIn;

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 'Children';
}

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

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

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