diff --git a/lib/API/Repository/SiblingRangeResolver.php b/lib/API/Repository/SiblingRangeResolver.php new file mode 100644 index 00000000..7997ef9d --- /dev/null +++ b/lib/API/Repository/SiblingRangeResolver.php @@ -0,0 +1,491 @@ +repository = $repository; + } + + /** + * @throws \Exception + */ + public function resolveQuery(ValueObject $value, Query $query, string $rangeType): Query + { + $query = clone $query; + + $this->modifyQuery($value, $query, $rangeType); + + return $query; + } + + /** + * @throws \Exception + */ + public function modifyQuery(ValueObject $value, Query $query, string $rangeType): void + { + $query->filter = new LogicalAnd([ + $query->filter, + $this->resolveCriterion( + $value, + $query->sortClauses, + $rangeType + ) + ]); + + $query->sortClauses = $this->resolveSortClauses( + $value, + $query->sortClauses, + $rangeType + ); + } + + /** + * @throws \Exception + */ + public function resolveCriterion(ValueObject $value, array $sortClauses, string $rangeType): CriterionInterface + { + $this->validateRange($rangeType); + $tieBreaker = $this->getTieBreakerCriterion($value, $rangeType); + + if (empty($sortClauses)) { + return $tieBreaker; + } + + $count = count($sortClauses); + $criteria = []; + + for ($i = $count; $i >= 1; $i--) { + $groupCriteria = $this->resolveGroupCriteria($value, $sortClauses, $rangeType, $i); + $groupCriteria[] = $tieBreaker; + + $criteria[] = new LogicalAnd($groupCriteria); + } + + $primarySortClause = $sortClauses[0]; + $operator = $this->resolveOperator($primarySortClause, $rangeType, false); + $criteria[] = $this->resolveCriterionForSortClause($value, $primarySortClause, $operator); + + return new LogicalOr($criteria); + } + + /** + * @param \eZ\Publish\API\Repository\Values\Content\Query\SortClause[] $sortClauses + * + * @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause[] + */ + public function resolveSortClauses(ValueObject $value, array $sortClauses, string $rangeType): array + { + $this->validateRange($rangeType); + $newSortClauses = []; + + foreach ($sortClauses as $sortClause) { + $newSortClause = clone $sortClause; + + if ($rangeType === self::RangeTypePreceding) { + $newSortClause->direction = $this->reverseDirection($sortClause); + } + + $newSortClauses[] = $newSortClause; + } + + $newSortClauses[] = $this->getTieBreakerSortClause($value, $rangeType); + + return $newSortClauses; + } + + /** + * @throws \Exception + */ + private function resolveGroupCriteria(ValueObject $value, array $sortClauses, string $rangeType, int $count): array + { + $criteria = []; + + for ($i = 0; $i < $count; $i++) { + $sortClause = $sortClauses[$i]; + $operator = $this->resolveOperator($sortClause, $rangeType, true); + + $criteria[] = $this->resolveCriterionForSortClause($value, $sortClause, $operator); + } + + return $criteria; + } + + /** + * @throws \Exception + */ + private function resolveCriterionForSortClause( + ValueObject $value, + SortClause $sortClause, + string $operator + ): CriterionInterface { + $sortClauseClass = get_class($sortClause); + + switch ($sortClauseClass) { + case ContentId::class: + return new ContentIdCriterion($operator, $this->getContentInfo($value)->id); + case ContentName::class: + return new ContentNameCriterion($operator, $this->getContentName($value)); + case DateModified::class: + return new DateMetadata( + DateMetadata::MODIFIED, + $operator, + $this->getContentInfo($value)->modificationDate->getTimestamp() + ); + case DatePublished::class: + return new DateMetadata( + DateMetadata::CREATED, + $operator, + $this->getContentInfo($value)->modificationDate->getTimestamp() + ); + case Depth::class: + return new DepthCriterion($operator, $this->getLocation($value)->depth); + case Field::class: + /** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Target\FieldTarget $targetData */ + $targetData = $sortClause->targetData; + + // todo logical and with content type identifier? + return new FieldCriterion( + $targetData->fieldIdentifier, + $operator, + $this->getFieldValue($value, $targetData->fieldIdentifier) + ); + case LocationId::class: + return new LocationIdCriterion($operator, $this->getLocation($value)->id); + case Priority::class: + return new PriorityCriterion($operator, $this->getLocation($value)->priority); + } + + throw new RuntimeException( + 'Sort clause "' . $sortClauseClass . '" is not supported' + ); + } + + /** + * @throws \Exception + */ + private function getLocation(ValueObject $value): Location + { + if ($value instanceof Location) { + return $value; + } + + if ($value instanceof SiteLocation) { + return $value->innerLocation; + } + + if ($value instanceof Content) { + return $this->repository->sudo( + function (Repository $repository) use ($value): Location { + return $repository->getLocationService()->loadLocation( + $value->contentInfo->mainLocationId + ); + } + ); + } + + if ($value instanceof SiteContent) { + return $value->mainLocation->innerLocation; + } + + throw new RuntimeException( + 'Value "' . get_class($value) . '" is not supported' + ); + } + + private function getContent(ValueObject $value): Content + { + if ($value instanceof Location) { + return $value->getContent(); + } + + if ($value instanceof SiteLocation) { + return $value->content->innerContent; + } + + if ($value instanceof Content) { + return $value; + } + + if ($value instanceof SiteContent) { + return $value->innerContent; + } + + throw new RuntimeException( + 'Value "' . get_class($value) . '" is not supported' + ); + } + + private function getContentInfo(ValueObject $value): ContentInfo + { + if ($value instanceof Location || $value instanceof Content) { + return $value->contentInfo; + } + + if ($value instanceof SiteLocation || $value instanceof SiteContent) { + return $value->contentInfo->innerContentInfo; + } + + throw new RuntimeException( + 'Value "' . get_class($value) . '" is not supported' + ); + } + + private function getContentName(ValueObject $value): string + { + return $this->getContent($value)->getName(); + } + + /** + * @return mixed + */ + private function getFieldValue(ValueObject $value, string $identifier) + { + $content = $this->getContent($value); + + $field = $content->getField($identifier); + + if ($field === null) { + throw new RuntimeException( + 'Field "' . $identifier . '" not found on the given Content' + ); + } + + switch ($field->fieldTypeIdentifier) { + case 'ezstring': + /** @var \eZ\Publish\Core\FieldType\TextLine\Value $value */ + $value = $field->value; + + return $value->text; + case 'eztext': + /** @var \eZ\Publish\Core\FieldType\TextBlock\Value $value */ + $value = $field->value; + + return $value->text; + case 'ezdate': + /** @var \eZ\Publish\Core\FieldType\Date\Value $value */ + $value = $field->value; + + if ($value->date === null) { + return null; + } + + return $value->date->format('Y-m-d\\Z'); + case 'ezdatetime': + /** @var \eZ\Publish\Core\FieldType\DateAndTime\Value $value */ + $value = $field->value; + + if ($value->value === null) { + return null; + } + + return $value->value->getTimestamp(); + case 'eztime': + /** @var \eZ\Publish\Core\FieldType\Time\Value $value */ + $value = $field->value; + + return $value->time; + case 'ezemail': + /** @var \eZ\Publish\Core\FieldType\EmailAddress\Value $value */ + $value = $field->value; + + return $value->email; + case 'ezinteger': + /** @var \eZ\Publish\Core\FieldType\Integer\Value $value */ + $value = $field->value; + + return $value->value; + case 'ezfloat': + /** @var \eZ\Publish\Core\FieldType\Float\Value $value */ + $value = $field->value; + + return $value->value; + case 'ezboolean': + /** @var \eZ\Publish\Core\FieldType\Checkbox\Value $value */ + $value = $field->value; + + return $value->bool; + case 'ezisbn': + /** @var \eZ\Publish\Core\FieldType\ISBN\Value $value */ + $value = $field->value; + + return $value->isbn; + } + + throw new RuntimeException( + 'Field type "' . $field->fieldTypeIdentifier . '" is not supported' + ); + } + + private function resolveOperator(SortClause $sortClause, string $rangeType, bool $inclusive): string + { + if ($rangeType === self::RangeTypeFollowing) { + return $this->resolveOperatorForFollowing($sortClause, $inclusive); + } + + return $this->resolveOperatorForPreceding($sortClause, $inclusive); + } + + private function resolveOperatorForFollowing(SortClause $sortClause, bool $inclusive): string + { + if ($sortClause->direction === Query::SORT_ASC) { + return $inclusive ? Operator::GTE : Operator::GT; + } + + return $inclusive ? Operator::LTE : Operator::LT; + } + + private function resolveOperatorForPreceding(SortClause $sortClause, bool $inclusive): string + { + if ($sortClause->direction === Query::SORT_ASC) { + return $inclusive ? Operator::LTE : Operator::LT; + } + + return $inclusive ? Operator::GTE : Operator::GT; + } + + private function getTieBreakerCriterion(ValueObject $value, string $rangeType): CriterionInterface + { + $operator = $this->getTieBreakerCriterionOperator($rangeType); + + if ($value instanceof Content || $value instanceof SiteContent) { + return new ContentIdCriterion($operator, $value->id); + } + + if ($value instanceof Location || $value instanceof SiteLocation) { + return new LocationIdCriterion($operator, $value->id); + } + + throw new RuntimeException( + 'Value "' . get_class($value) . '" is not supported' + ); + } + + private function getTieBreakerCriterionOperator(string $rangeType): string + { + if ($rangeType === self::RangeTypeFollowing) { + return Operator::GT; + } + + return Operator::LT; + } + + private function reverseDirection(SortClause $sortClause): string + { + if ($sortClause->direction === Query::SORT_ASC) { + return Query::SORT_DESC; + } + + return Query::SORT_ASC; + } + + private function getTieBreakerSortClause(ValueObject $value, string $rangeType): SortClause + { + $direction = $this->getSortClauseDirection($rangeType); + + if ($value instanceof Content || $value instanceof SiteContent) { + return new ContentId($direction); + } + + if ($value instanceof Location || $value instanceof SiteLocation) { + return new LocationId($direction); + } + + throw new RuntimeException( + 'Value "' . get_class($value) . '" is not supported' + ); + } + + private function getSortClauseDirection(string $rangeType): string + { + if ($rangeType === self::RangeTypeFollowing) { + return Query::SORT_ASC; + } + + return Query::SORT_DESC; + } + + private function validateRange(string $rangeType): void + { + if ($rangeType === self::RangeTypeFollowing) { + return; + } + + if ($rangeType === self::RangeTypePreceding) { + return; + } + + throw new RuntimeException( + 'Unknown range "' . $rangeType . '"' + ); + } +} diff --git a/lib/Core/Search/Solr/ResultExtractor.php b/lib/Core/Search/Solr/ResultExtractor.php index 9ff759ef..7290793b 100644 --- a/lib/Core/Search/Solr/ResultExtractor.php +++ b/lib/Core/Search/Solr/ResultExtractor.php @@ -16,9 +16,9 @@ public function extract($data, array $facetBuilders = []) { $searchResult = $this->extractSearchResult($data, $facetBuilders); - if (!isset($data->facets) || $data->facets->count === 0) { - return $searchResult; - } +// if (!isset($data->facets) || $data->facets->count === 0) { +// return $searchResult; +// } foreach ($this->filterNewFacetBuilders($facetBuilders) as $facetBuilder) { $identifier = \spl_object_hash($facetBuilder); diff --git a/tests/lib/Unit/API/Repository/SiblingRangeResolverTest.php b/tests/lib/Unit/API/Repository/SiblingRangeResolverTest.php new file mode 100644 index 00000000..7d8f7be5 --- /dev/null +++ b/tests/lib/Unit/API/Repository/SiblingRangeResolverTest.php @@ -0,0 +1,1874 @@ + 'field', + 'fieldTypeIdentifier' => 'ezstring', + 'languageCode' => 'cro-HR', + 'value' => new TextLineValue('Zagreb'), + ]); + + return [ + [ + 42, + $field, + [], + SiblingRangeResolver::RangeTypeFollowing, + new ContentId(Operator::GT, 42), + ], + [ + 42, + $field, + [], + SiblingRangeResolver::RangeTypePreceding, + new ContentId(Operator::LT, 42), + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new ContentName(Operator::GTE, 'Netgen'), + new ContentId(Operator::GT, 42), + ]), + new ContentName(Operator::GT, 'Netgen'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new ContentName(Operator::LTE, 'Netgen'), + new ContentId(Operator::GT, 42), + ]), + new ContentName(Operator::LT, 'Netgen'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new ContentName(Operator::LTE, 'Netgen'), + new ContentId(Operator::LT, 42), + ]), + new ContentName(Operator::LT, 'Netgen'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new ContentName(Operator::GTE, 'Netgen'), + new ContentId(Operator::LT, 42), + ]), + new ContentName(Operator::GT, 'Netgen'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new ContentId(Operator::GTE, 42), + new ContentId(Operator::GT, 42), + ]), + new ContentId(Operator::GT, 42), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new ContentId(Operator::LTE, 42), + new ContentId(Operator::GT, 42), + ]), + new ContentId(Operator::LT, 42), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new ContentId(Operator::LTE, 42), + new ContentId(Operator::LT, 42), + ]), + new ContentId(Operator::LT, 42), + ]), + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new ContentId(Operator::GTE, 42), + new ContentId(Operator::LT, 42), + ]), + new ContentId(Operator::GT, 42), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::MODIFIED, Operator::GTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new DateMetadata(DateMetadata::MODIFIED, Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::MODIFIED, Operator::LTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new DateMetadata(DateMetadata::MODIFIED, Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::MODIFIED, Operator::LTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new DateMetadata(DateMetadata::MODIFIED, Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::MODIFIED, Operator::GTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new DateMetadata(DateMetadata::MODIFIED, Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::CREATED, Operator::GTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new DateMetadata(DateMetadata::CREATED, Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::CREATED, Operator::LTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new DateMetadata(DateMetadata::CREATED, Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::CREATED, Operator::LTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new DateMetadata(DateMetadata::CREATED, Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new DateMetadata(DateMetadata::CREATED, Operator::GTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new DateMetadata(DateMetadata::CREATED, Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Depth(Operator::GTE, 6), + new ContentId(Operator::GT, 42), + ]), + new Depth(Operator::GT, 6), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Depth(Operator::LTE, 6), + new ContentId(Operator::GT, 42), + ]), + new Depth(Operator::LT, 6), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Depth(Operator::LTE, 6), + new ContentId(Operator::LT, 42), + ]), + new Depth(Operator::LT, 6), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Depth(Operator::GTE, 6), + new ContentId(Operator::LT, 42), + ]), + new Depth(Operator::GT, 6), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new LocationId(Operator::GTE, 24), + new ContentId(Operator::GT, 42), + ]), + new LocationId(Operator::GT, 24), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new LocationId(Operator::LTE, 24), + new ContentId(Operator::GT, 42), + ]), + new LocationId(Operator::LT, 24), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new LocationId(Operator::LTE, 24), + new ContentId(Operator::LT, 42), + ]), + new LocationId(Operator::LT, 24), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new LocationId(Operator::GTE, 24), + new ContentId(Operator::LT, 42), + ]), + new LocationId(Operator::GT, 24), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Priority(Operator::GTE, 4), + new ContentId(Operator::GT, 42), + ]), + new Priority(Operator::GT, 4), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Priority(Operator::LTE, 4), + new ContentId(Operator::GT, 42), + ]), + new Priority(Operator::LT, 4), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Priority(Operator::LTE, 4), + new ContentId(Operator::LT, 42), + ]), + new Priority(Operator::LT, 4), + ]), + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Priority(Operator::GTE, 4), + new ContentId(Operator::LT, 42), + ]), + new Priority(Operator::GT, 4), + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestResolveCriterion + * + * @throws \Exception + */ + public function testResolveCriterion( + int $id, + Field $field, + array $sortClauses, + string $rangeType, + CriterionInterface $expectedCriterion + ): void { + $content = $this->getContent($id, $field); + + $actualCriterion = $this->getServiceUnderTest()->resolveCriterion( + $content, + $sortClauses, + $rangeType + ); + + self::assertEquals($expectedCriterion, $actualCriterion); + } + + /** + * @throws \Exception + */ + public function providerForTestResolveCriterionField(): array + { + $textLineField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezstring', + 'languageCode' => 'cro-HR', + 'value' => new TextLineValue('Zagreb'), + ]); + $textBlockField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'eztext', + 'languageCode' => 'cro-HR', + 'value' => new TextBlockValue('Zagreb'), + ]); + $dateField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezdate', + 'languageCode' => 'cro-HR', + 'value' => new DateValue(new DateTime('@' . self::Timestamp)), + ]); + $dateAndTimeField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezdatetime', + 'languageCode' => 'cro-HR', + 'value' => new DateAndTimeValue(new DateTime('@' . self::Timestamp)), + ]); + $timeField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'eztime', + 'languageCode' => 'cro-HR', + 'value' => TimeValue::fromDateTime(new DateTime('@' . self::Timestamp)), + ]); + $mailAddressField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezemail', + 'languageCode' => 'cro-HR', + 'value' => new EmailAddressValue('test@netgen.io'), + ]); + $integerField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezinteger', + 'languageCode' => 'cro-HR', + 'value' => new IntegerValue(22), + ]); + $floatField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezfloat', + 'languageCode' => 'cro-HR', + 'value' => new FloatValue(3.14), + ]); + $checkboxField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezboolean', + 'languageCode' => 'cro-HR', + 'value' => new CheckboxValue(true), + ]); + $isbnField = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezisbn', + 'languageCode' => 'cro-HR', + 'value' => new ISBNValue('9780061936456'), + ]); + + return [ + [ + 42, + $textLineField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $textLineField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $textLineField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $textLineField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $textBlockField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $textBlockField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $textBlockField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'Zagreb'), + ]), + ], + [ + 42, + $textBlockField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'Zagreb'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'Zagreb'), + ]), + ], + [ + 42, + $dateField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, '2021-01-31Z'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, '2021-01-31Z'), + ]), + ], + [ + 42, + $dateField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, '2021-01-31Z'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, '2021-01-31Z'), + ]), + ], + [ + 42, + $dateField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, '2021-01-31Z'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, '2021-01-31Z'), + ]), + ], + [ + 42, + $dateField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, '2021-01-31Z'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, '2021-01-31Z'), + ]), + ], + [ + 42, + $dateAndTimeField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $dateAndTimeField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, self::Timestamp), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $dateAndTimeField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, self::Timestamp), + ]), + ], + [ + 42, + $dateAndTimeField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, self::Timestamp), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, self::Timestamp), + ]), + ], + [ + 42, + $timeField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 35938), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 35938), + ]), + ], + [ + 42, + $timeField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 35938), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 35938), + ]), + ], + [ + 42, + $timeField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 35938), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 35938), + ]), + ], + [ + 42, + $timeField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 35938), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 35938), + ]), + ], + [ + 42, + $mailAddressField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'test@netgen.io'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'test@netgen.io'), + ]), + ], + [ + 42, + $mailAddressField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'test@netgen.io'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'test@netgen.io'), + ]), + ], + [ + 42, + $mailAddressField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 'test@netgen.io'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 'test@netgen.io'), + ]), + ], + [ + 42, + $mailAddressField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 'test@netgen.io'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 'test@netgen.io'), + ]), + ], + [ + 42, + $integerField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 22), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 22), + ]), + ], + [ + 42, + $integerField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 22), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 22), + ]), + ], + [ + 42, + $integerField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 22), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 22), + ]), + ], + [ + 42, + $integerField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 22), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 22), + ]), + ], + [ + 42, + $floatField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 3.14), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, 3.14), + ]), + ], + [ + 42, + $floatField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 3.14), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, 3.14), + ]), + ], + [ + 42, + $floatField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, 3.14), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, 3.14), + ]), + ], + [ + 42, + $floatField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, 3.14), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, 3.14), + ]), + ], + [ + 42, + $checkboxField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, true), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, true), + ]), + ], + [ + 42, + $checkboxField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, true), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, true), + ]), + ], + [ + 42, + $checkboxField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, true), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, true), + ]), + ], + [ + 42, + $checkboxField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, true), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, true), + ]), + ], + [ + 42, + $isbnField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, '9780061936456'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::GT, '9780061936456'), + ]), + ], + [ + 42, + $isbnField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, '9780061936456'), + new ContentId(Operator::GT, 42), + ]), + new Criterion\Field('field', Operator::LT, '9780061936456'), + ]), + ], + [ + 42, + $isbnField, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::LTE, '9780061936456'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::LT, '9780061936456'), + ]), + ], + [ + 42, + $isbnField, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + new LogicalOr([ + new LogicalAnd([ + new Criterion\Field('field', Operator::GTE, '9780061936456'), + new ContentId(Operator::LT, 42), + ]), + new Criterion\Field('field', Operator::GT, '9780061936456'), + ]), + ], + ]; + } + + /** + * @dataProvider providerForTestResolveCriterionField + * + * @throws \Exception + */ + public function testResolveCriterionField( + int $id, + Field $field, + array $sortClauses, + string $rangeType, + CriterionInterface $expectedCriterion + ): void { + $content = $this->getContent($id, $field); + + $actualCriterion = $this->getServiceUnderTest()->resolveCriterion( + $content, + $sortClauses, + $rangeType + ); + + self::assertEquals($expectedCriterion, $actualCriterion); + } + + /** + * @throws \Exception + */ + public function testResolveCriterionWithNonExistentField(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Field "field" not found on the given Content'); + + $field = new Field([ + 'fieldDefIdentifier' => 'hill', + 'fieldTypeIdentifier' => 'ezisbn', + 'languageCode' => 'cro-HR', + 'value' => new ISBNValue('9780061936456'), + ]); + + $content = $this->getContent(42, $field); + + $this->getServiceUnderTest()->resolveCriterion( + $content, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing + ); + } + + /** + * @throws \Exception + */ + public function testResolveCriterionWithUnsupportedFieldType(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Field type "ezobjectrelationlist" is not supported'); + + $field = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezobjectrelationlist', + 'languageCode' => 'cro-HR', + 'value' => new RelationListValue(), + ]); + + $content = $this->getContent(42, $field); + + $this->getServiceUnderTest()->resolveCriterion( + $content, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing + ); + } + + public function providerForTestResolveSortClauses(): array + { + $field = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezstring', + 'languageCode' => 'cro-HR', + 'value' => new TextLineValue('Zagreb'), + ]); + + return [ + [ + 42, + $field, + [], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentName(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentName(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\ContentName(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentName(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\ContentName(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentId(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentId(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\ContentId(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\ContentId(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\ContentId(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\DateModified(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\DateModified(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\DateModified(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DateModified(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\DateModified(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\DatePublished(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\DatePublished(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\DatePublished(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\DatePublished(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\DatePublished(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Depth(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Depth(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Field('type', 'field', Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Id(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Id(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Id(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Id(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Id(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypeFollowing, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_ASC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + [ + 42, + $field, + [ + new SortClause\Location\Priority(Query::SORT_DESC), + ], + SiblingRangeResolver::RangeTypePreceding, + [ + new SortClause\Location\Priority(Query::SORT_ASC), + new SortClause\ContentId(Query::SORT_DESC), + ], + ], + ]; + } + + /** + * @dataProvider providerForTestResolveSortClauses + * + * @throws \Exception + */ + public function testResolveSortClauses( + int $id, + Field $field, + array $sortClauses, + string $rangeType, + array $expectedSortClauses + ): void { + $content = $this->getContent($id, $field); + + $actualSortClauses = $this->getServiceUnderTest()->resolveSortClauses( + $content, + $sortClauses, + $rangeType + ); + + self::assertEquals($expectedSortClauses, $actualSortClauses); + } + + /** + * @throws \Exception + */ + public function testResolverCriterionWithUnsupportedSortClause(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Sort clause "' . SortClause\MapLocationDistance::class . '" is not supported'); + + $field = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezstring', + 'languageCode' => 'cro-HR', + 'value' => new TextLineValue('Zagreb'), + ]); + + $content = $this->getContent(42, $field); + + $this->getServiceUnderTest()->resolveCriterion( + $content, + [ + new SortClause\MapLocationDistance('type', 'field', 16, 42), + ], + SiblingRangeResolver::RangeTypeFollowing + ); + } + + /** + * @throws \Exception + */ + public function testResolverCriterionWithUnsupportedRange(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unknown range "SomeRange"'); + + $field = new Field([ + 'fieldDefIdentifier' => 'field', + 'fieldTypeIdentifier' => 'ezstring', + 'languageCode' => 'cro-HR', + 'value' => new TextLineValue('Zagreb'), + ]); + + $content = $this->getContent(42, $field); + + $this->getServiceUnderTest()->resolveCriterion( + $content, + [ + new SortClause\Field('type', 'field', Query::SORT_DESC), + ], + 'SomeRange' + ); + } + + public function testModifyQuery(): void + { + + } + + public function testResolveQuery(): void + { + + } + + /** + * @throws \Exception + */ + protected function getContent(int $id, Field $field): APIContent + { + return new Content([ + 'versionInfo' => new VersionInfo([ + 'contentInfo' => new ContentInfo([ + 'id' => $id, + 'modificationDate' => new DateTime('@' . self::Timestamp), + 'mainLanguageCode' => 'cro-HR', + ]), + 'initialLanguageCode' => 'cro-HR', + 'names' => [ + 'cro-HR' => 'Netgen', + ], + + ]), + 'internalFields' => [ + 'field' => $field, + ], + ]); + } + + protected function getLocation(): APILocation + { + return new Location([ + 'id' => 24, + 'depth' => 6, + 'priority' => 4, + ]); + } + + protected function getServiceUnderTest(): SiblingRangeResolver + { + return new SiblingRangeResolver($this->getRepositoryMock()); + } + + protected function getRepositoryMock() + { + $repositoryMock = $this + ->getMockBuilder(CoreRepository::class) + ->disableOriginalConstructor() + ->getMock(); + + $repoContent = $this->getLocation(); + $repositoryMock->method('sudo')->willReturn($repoContent); + + return $repositoryMock; + } +}