Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/bundle/Core/ApiLoader/RepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function buildRepository(
PersistenceHandler $persistenceHandler,
SearchHandler $searchHandler,
BackgroundIndexer $backgroundIndexer,
Mapper\SearchHitMapperRegistryInterface $searchHitMapperRegistry,
RelationProcessor $relationProcessor,
FieldTypeRegistry $fieldTypeRegistry,
PasswordHashService $passwordHashService,
Expand All @@ -99,6 +100,7 @@ public function buildRepository(
$persistenceHandler,
$searchHandler,
$backgroundIndexer,
$searchHitMapperRegistry,
$relationProcessor,
$fieldTypeRegistry,
$passwordHashService,
Expand Down
15 changes: 12 additions & 3 deletions src/contracts/Repository/Decorator/SearchServiceDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Search\SearchContextInterface;

abstract class SearchServiceDecorator implements SearchService
{
/** @var \Ibexa\Contracts\Core\Repository\SearchService */
protected $innerService;
protected SearchService $innerService;

public function __construct(SearchService $innerService)
{
$this->innerService = $innerService;
}

public function find(Query $query, SearchContextInterface $context = null): SearchResult
{
return $this->innerService->find($query, $context);
}

public function findContent(
Query $query,
array $languageFilter = [],
bool $filterOnUserPermissions = true
): SearchResult {
return $this->innerService->findContent($query, $languageFilter, $filterOnUserPermissions);
return $this->innerService->findContent(
$query,
$languageFilter,
$filterOnUserPermissions
);
}

public function findContentInfo(
Expand Down
3 changes: 3 additions & 0 deletions src/contracts/Repository/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Search\SearchContextInterface;

/**
* Search service.
Expand Down Expand Up @@ -114,6 +115,8 @@ interface SearchService
*/
public const CAPABILITY_AGGREGATIONS = 128;

public function find(Query $query, SearchContextInterface $context = null): SearchResult;

/**
* Finds content objects for the given query.
*
Expand Down
62 changes: 62 additions & 0 deletions src/contracts/Repository/Values/Search/SearchContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

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

namespace Ibexa\Contracts\Core\Repository\Values\Search;

final class SearchContext implements SearchContextInterface
{
/** @var array{}|array{languages: array<string>, useAlwaysAvailable: bool} */
private array $languageFilter;

private bool $filterOnUserPermissions;

/** @var array<string> */
private array $documentTypeIdentifiers;

/**
* @param array{}|array{languages: array<string>, useAlwaysAvailable: bool} $languageFilter
* @param array<string> $documentTypeIdentifiers
*/
public function __construct(
array $languageFilter = [],
bool $filterOnUserPermissions = true,
array $documentTypeIdentifiers = []
) {
$this->languageFilter = $languageFilter;
$this->filterOnUserPermissions = $filterOnUserPermissions;
$this->documentTypeIdentifiers = $documentTypeIdentifiers;
}

public function setLanguageFilter(array $languageFilter): void
{
$this->languageFilter = $languageFilter;
}

public function getLanguageFilter(): array
{
return $this->languageFilter;
}

public function setFilterOnUserPermissions(bool $filterOnUserPermissions): void
{
$this->filterOnUserPermissions = $filterOnUserPermissions;
}

public function doesFilterOnUserPermissions(): bool
{
return $this->filterOnUserPermissions;
}

/**
* @return array<string>
*/
public function getDocumentTypeIdentifiers(): array
{
return $this->documentTypeIdentifiers;
}
}
31 changes: 31 additions & 0 deletions src/contracts/Repository/Values/Search/SearchContextInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

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

namespace Ibexa\Contracts\Core\Repository\Values\Search;

interface SearchContextInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that Context implies that there might be parameters that are outside of our current expectations that are used by third party code. In my opinion it should be flexible enough that any underlying search engine could pick parameters that it is interested in.

{
/**
* @param array{}|array{languages: array<string>, useAlwaysAvailable: bool} $languageFilter
*/
Comment on lines +13 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @param array{}|array{languages: array<string>, useAlwaysAvailable: bool} $languageFilter
*/
/**
* @param array{languages?: array<string>, useAlwaysAvailable?: bool} $languageFilter
*/

public function setLanguageFilter(array $languageFilter): void;

/**
* @return array{}|array{languages: array<string>, useAlwaysAvailable: bool}
*/
public function getLanguageFilter(): array;

public function setFilterOnUserPermissions(bool $filterOnUserPermissions): void;

public function doesFilterOnUserPermissions(): bool;

/**
* @return array<string>
*/
public function getDocumentTypeIdentifiers(): array;
}
8 changes: 8 additions & 0 deletions src/contracts/Search/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@
use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Search\SearchContextInterface;

/**
* The Search handler retrieves sets of of Content objects, based on a
* set of criteria.
*/
interface Handler
{
/**
* Finds objects for the given query.
*
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if Query criterion is not applicable to its target
*/
public function find(Query $query, SearchContextInterface $context = null);

/**
* Finds content objects for the given query.
*
Expand Down
3 changes: 3 additions & 0 deletions src/lib/Base/Container/ApiLoader/RepositoryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Ibexa\Core\FieldType\FieldTypeRegistry;
use Ibexa\Core\Repository\Helper\RelationProcessor;
use Ibexa\Core\Repository\Mapper;
use Ibexa\Core\Repository\Mapper\SearchHitMapperRegistryInterface;
use Ibexa\Core\Repository\Permission\LimitationService;
use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface;
use Ibexa\Core\Repository\User\PasswordValidatorInterface;
Expand Down Expand Up @@ -67,6 +68,7 @@ public function buildRepository(
PersistenceHandler $persistenceHandler,
SearchHandler $searchHandler,
BackgroundIndexer $backgroundIndexer,
SearchHitMapperRegistryInterface $searchHitMapperRegistry,
RelationProcessor $relationProcessor,
FieldTypeRegistry $fieldTypeRegistry,
PasswordHashService $passwordHashService,
Expand All @@ -89,6 +91,7 @@ public function buildRepository(
$persistenceHandler,
$searchHandler,
$backgroundIndexer,
$searchHitMapperRegistry,
$relationProcessor,
$fieldTypeRegistry,
$passwordHashService,
Expand Down
54 changes: 51 additions & 3 deletions src/lib/Repository/Mapper/ContentDomainMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo;
use Ibexa\Contracts\Core\Repository\Values\Content\Field;
use Ibexa\Contracts\Core\Repository\Values\Content\Location as APILocation;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo as APIVersionInfo;
use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType;
use Ibexa\Contracts\Core\Repository\Values\Search\SearchContextInterface;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
use Ibexa\Core\Base\Exceptions\InvalidArgumentType;
use Ibexa\Core\Base\Exceptions\InvalidArgumentValue;
Expand All @@ -46,7 +48,7 @@
*
* @internal Meant for internal use by Repository.
*/
class ContentDomainMapper extends ProxyAwareDomainMapper implements LoggerAwareInterface
class ContentDomainMapper extends ProxyAwareDomainMapper implements LoggerAwareInterface, SearchHitMapperInterface
{
use LoggerAwareTrait;

Expand Down Expand Up @@ -610,8 +612,8 @@ public function buildContentDomainObjectsOnSearchResult(SearchResult $result, ar
$info = $hit->valueObject;
$contentIds[] = $info->id;
$contentTypeIds[] = $info->contentTypeId;
// Unless we are told to load all languages, we add main language to translations so they are loaded too
// Might in some case load more languages then intended, but prioritised handling will pick right one
// Unless we are told to load all languages, we add main language to translations, so they are loaded too
// Might in some case load more languages than intended, but prioritised handling will pick right one
if (!empty($languageFilter['languages']) && $useAlwaysAvailable && $info->alwaysAvailable) {
$translations[] = $info->mainLanguageCode;
}
Expand Down Expand Up @@ -893,6 +895,52 @@ private function isRootLocation(SPILocation $spiLocation): bool
{
return $spiLocation->id === $spiLocation->parentId;
}

public function buildObjectOnSearchHit(SearchHit $hit, SearchContextInterface $context = null)
{
return $this->buildContentDomainObjectsOnSearchHit($hit, $context);
}

public function supports(SearchHit $hit, SearchContextInterface $context = null): bool
{
return $hit->valueObject instanceof SPIContent\ContentInfo;
}

private function buildContentDomainObjectsOnSearchHit(SearchHit $hit, SearchContextInterface $context = null): ?Content
{
/** @var \Ibexa\Contracts\Core\Persistence\Content\ContentInfo $info */
$info = $hit->valueObject;
$languageFilter = [];

if ($context !== null) {
$languageFilter = $context->getLanguageFilter();
}

$translations = $languageFilter['languages'] ?? [];
$useAlwaysAvailable = $languageFilter['useAlwaysAvailable'] ?? true;

if (!empty($languageFilter['languages']) && $useAlwaysAvailable && $info->alwaysAvailable) {
$translations[] = $info->mainLanguageCode;
}

try {
$content = $this->contentHandler->load($info->id, null, $translations);
$contentType = $this->contentTypeHandler->load($info->contentTypeId);
$languages = $languageFilter['languages'] ?? [];

return $this->buildContentDomainObject(
$content,
$this->contentTypeDomainMapper->buildContentTypeDomainObject(
$contentType,
$languages
),
$languages,
$useAlwaysAvailable ? $info->mainLanguageCode : null
);
} catch (NotFoundException $e) {
return null;
}
}
}

class_alias(ContentDomainMapper::class, 'eZ\Publish\Core\Repository\Mapper\ContentDomainMapper');
20 changes: 20 additions & 0 deletions src/lib/Repository/Mapper/SearchHitMapperInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;
use Ibexa\Contracts\Core\Repository\Values\Search\SearchContextInterface;

interface SearchHitMapperInterface
{
/**
* @return mixed
*/
public function buildObjectOnSearchHit(SearchHit $hit, SearchContextInterface $context = null);

public function supports(SearchHit $hit, SearchContextInterface $context = null): bool;
}
56 changes: 56 additions & 0 deletions src/lib/Repository/Mapper/SearchHitMapperRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;

final class SearchHitMapperRegistry implements SearchHitMapperRegistryInterface
{
/** @var iterable<\Ibexa\Core\Repository\Mapper\SearchHitMapperInterface> */
private iterable $mappers;

/**
* @param iterable<\Ibexa\Core\Repository\Mapper\SearchHitMapperInterface> $mappers
*/
public function __construct(iterable $mappers)
{
$this->mappers = $mappers;
}

public function hasMapper(SearchHit $hit): bool
{
return $this->findMappers($hit) !== null;
}

public function getMapper(SearchHit $hit): SearchHitMapperInterface
{
if (!$this->hasMapper($hit)) {
throw new InvalidArgumentException(
'$hit',
sprintf(
'undefined %s for search hit %s',
SearchHitMapperInterface::class,
get_debug_type($hit->valueObject)
)
);
}

return $this->findMappers($hit);
}

private function findMappers(SearchHit $hit): ?SearchHitMapperInterface
{
foreach ($this->mappers as $mapper) {
if ($mapper->supports($hit)) {
return $mapper;
}
}

return null;
}
}
16 changes: 16 additions & 0 deletions src/lib/Repository/Mapper/SearchHitMapperRegistryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace Ibexa\Core\Repository\Mapper;

use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit;

interface SearchHitMapperRegistryInterface
{
public function hasMapper(SearchHit $hit): bool;

public function getMapper(SearchHit $hit): SearchHitMapperInterface;
}
Loading