vendor/ezsystems/ezplatform-kernel/eZ/Publish/Core/Repository/SearchService.php line 34

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. declare(strict_types=1);
  7. namespace eZ\Publish\Core\Repository;
  8. use eZ\Publish\API\Repository\SearchService as SearchServiceInterface;
  9. use eZ\Publish\API\Repository\PermissionCriterionResolver;
  10. use eZ\Publish\API\Repository\Values\Content\Content;
  11. use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
  12. use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
  13. use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalOperator;
  14. use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Location as LocationCriterion;
  15. use eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location as LocationSortClause;
  16. use eZ\Publish\API\Repository\Values\Content\Query;
  17. use eZ\Publish\API\Repository\Values\Content\LocationQuery;
  18. use eZ\Publish\API\Repository\Repository as RepositoryInterface;
  19. use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
  20. use eZ\Publish\Core\Base\Exceptions\NotFoundException;
  21. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
  22. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
  23. use eZ\Publish\Core\Repository\Mapper\ContentDomainMapper;
  24. use eZ\Publish\SPI\Search\Capable;
  25. use eZ\Publish\Core\Search\Common\BackgroundIndexer;
  26. use eZ\Publish\SPI\Search\Handler;
  27. /**
  28.  * Search service.
  29.  */
  30. class SearchService implements SearchServiceInterface
  31. {
  32.     /** @var \eZ\Publish\Core\Repository\Repository */
  33.     protected $repository;
  34.     /** @var \eZ\Publish\SPI\Search\Handler */
  35.     protected $searchHandler;
  36.     /** @var array */
  37.     protected $settings;
  38.     /** @var \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper */
  39.     protected $contentDomainMapper;
  40.     /** @var \eZ\Publish\API\Repository\PermissionCriterionResolver */
  41.     protected $permissionCriterionResolver;
  42.     /** @var \eZ\Publish\Core\Search\Common\BackgroundIndexer */
  43.     protected $backgroundIndexer;
  44.     /**
  45.      * Setups service with reference to repository object that created it & corresponding handler.
  46.      *
  47.      * @param \eZ\Publish\API\Repository\Repository $repository
  48.      * @param \eZ\Publish\SPI\Search\Handler $searchHandler
  49.      * @param \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper $contentDomainMapper
  50.      * @param \eZ\Publish\API\Repository\PermissionCriterionResolver $permissionCriterionResolver
  51.      * @param \eZ\Publish\Core\Search\Common\BackgroundIndexer $backgroundIndexer
  52.      * @param array $settings
  53.      */
  54.     public function __construct(
  55.         RepositoryInterface $repository,
  56.         Handler $searchHandler,
  57.         ContentDomainMapper $contentDomainMapper,
  58.         PermissionCriterionResolver $permissionCriterionResolver,
  59.         BackgroundIndexer $backgroundIndexer,
  60.         array $settings = []
  61.     ) {
  62.         $this->repository $repository;
  63.         $this->searchHandler $searchHandler;
  64.         $this->contentDomainMapper $contentDomainMapper;
  65.         // Union makes sure default settings are ignored if provided in argument
  66.         $this->settings $settings + [
  67.             //'defaultSetting' => array(),
  68.         ];
  69.         $this->permissionCriterionResolver $permissionCriterionResolver;
  70.         $this->backgroundIndexer $backgroundIndexer;
  71.     }
  72.     /**
  73.      * Finds content objects for the given query.
  74.      *
  75.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
  76.      *
  77.      * @param \eZ\Publish\API\Repository\Values\Content\Query $query
  78.      * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
  79.      *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
  80.      *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
  81.      * @param bool $filterOnUserPermissions if true only the objects which the user is allowed to read are returned.
  82.      *
  83.      * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
  84.      */
  85.     public function findContent(Query $query, array $languageFilter = [], bool $filterOnUserPermissions true): SearchResult
  86.     {
  87.         $result $this->internalFindContentInfo($query$languageFilter$filterOnUserPermissions);
  88.         $missingContentList $this->contentDomainMapper->buildContentDomainObjectsOnSearchResult($result$languageFilter);
  89.         foreach ($missingContentList as $missingContent) {
  90.             $this->backgroundIndexer->registerContent($missingContent);
  91.         }
  92.         return $result;
  93.     }
  94.     /**
  95.      * Finds contentInfo objects for the given query.
  96.      *
  97.      * @see SearchServiceInterface::findContentInfo()
  98.      * @since 5.4.5
  99.      *
  100.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
  101.      *
  102.      * @param \eZ\Publish\API\Repository\Values\Content\Query $query
  103.      * @param array $languageFilter - a map of filters for the returned fields.
  104.      *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
  105.      *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
  106.      * @param bool $filterOnUserPermissions if true (default) only the objects which is the user allowed to read are returned.
  107.      *
  108.      * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
  109.      */
  110.     public function findContentInfo(Query $query, array $languageFilter = [], bool $filterOnUserPermissions true): SearchResult
  111.     {
  112.         $result $this->internalFindContentInfo($query$languageFilter$filterOnUserPermissions);
  113.         foreach ($result->searchHits as $hit) {
  114.             $hit->valueObject $this->contentDomainMapper->buildContentInfoDomainObject(
  115.                 $hit->valueObject
  116.             );
  117.         }
  118.         return $result;
  119.     }
  120.     /**
  121.      * Finds SPI content info objects for the given query.
  122.      *
  123.      * Internal for use by {@link findContent} and {@link findContentInfo}.
  124.      *
  125.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
  126.      *
  127.      * @param \eZ\Publish\API\Repository\Values\Content\Query $query
  128.      * @param array $languageFilter - a map of filters for the returned fields.
  129.      *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
  130.      *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
  131.      * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
  132.      *
  133.      * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult With "raw" SPI contentInfo objects in result
  134.      */
  135.     protected function internalFindContentInfo(Query $query, array $languageFilter = [], $filterOnUserPermissions true)
  136.     {
  137.         if (!is_int($query->offset)) {
  138.             throw new InvalidArgumentType(
  139.                 '$query->offset',
  140.                 'integer',
  141.                 $query->offset
  142.             );
  143.         }
  144.         if (!is_int($query->limit)) {
  145.             throw new InvalidArgumentType(
  146.                 '$query->limit',
  147.                 'integer',
  148.                 $query->limit
  149.             );
  150.         }
  151.         $query = clone $query;
  152.         $query->filter $query->filter ?: new Criterion\MatchAll();
  153.         $this->validateContentCriteria([$query->query], '$query');
  154.         $this->validateContentCriteria([$query->filter], '$query');
  155.         $this->validateContentSortClauses($query);
  156.         if ($filterOnUserPermissions && !$this->addPermissionsCriterion($query->filter)) {
  157.             return new SearchResult(['time' => 0'totalCount' => 0]);
  158.         }
  159.         return $this->searchHandler->findContent($query$languageFilter);
  160.     }
  161.     /**
  162.      * Checks that $criteria does not contain Location criterions.
  163.      *
  164.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
  165.      *
  166.      * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion[] $criteria
  167.      * @param string $argumentName
  168.      */
  169.     protected function validateContentCriteria(array $criteria$argumentName)
  170.     {
  171.         foreach ($criteria as $criterion) {
  172.             if ($criterion instanceof LocationCriterion) {
  173.                 throw new InvalidArgumentException(
  174.                     $argumentName,
  175.                     'Location Criteria cannot be used in Content search'
  176.                 );
  177.             }
  178.             if ($criterion instanceof LogicalOperator) {
  179.                 $this->validateContentCriteria($criterion->criteria$argumentName);
  180.             }
  181.         }
  182.     }
  183.     /**
  184.      * Checks that $query does not contain Location sort clauses.
  185.      *
  186.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
  187.      *
  188.      * @param \eZ\Publish\API\Repository\Values\Content\Query $query
  189.      */
  190.     protected function validateContentSortClauses(Query $query)
  191.     {
  192.         foreach ($query->sortClauses as $sortClause) {
  193.             if ($sortClause instanceof LocationSortClause) {
  194.                 throw new InvalidArgumentException('$query''Location Sort Clauses cannot be used in Content search');
  195.             }
  196.         }
  197.     }
  198.     /**
  199.      * Performs a query for a single content object.
  200.      *
  201.      * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if the object was not found by the query or due to permissions
  202.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if criterion is not valid
  203.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if there is more than one result matching the criterions
  204.      *
  205.      * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
  206.      * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
  207.      *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
  208.      *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations.
  209.      * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
  210.      *
  211.      * @return \eZ\Publish\API\Repository\Values\Content\Content
  212.      */
  213.     public function findSingle(Criterion $filter, array $languageFilter = [], bool $filterOnUserPermissions true): Content
  214.     {
  215.         $this->validateContentCriteria([$filter], '$filter');
  216.         if ($filterOnUserPermissions && !$this->addPermissionsCriterion($filter)) {
  217.             throw new NotFoundException('Content''*');
  218.         }
  219.         $contentInfo $this->searchHandler->findSingle($filter$languageFilter);
  220.         return $this->repository->getContentService()->internalLoadContentById(
  221.             $contentInfo->id,
  222.             (!empty($languageFilter['languages']) ? $languageFilter['languages'] : null),
  223.             null,
  224.             (isset($languageFilter['useAlwaysAvailable']) ? $languageFilter['useAlwaysAvailable'] : true)
  225.         );
  226.     }
  227.     /**
  228.      * Suggests a list of values for the given prefix.
  229.      *
  230.      * @param string $prefix
  231.      * @param string[] $fieldPaths
  232.      * @param int $limit
  233.      * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
  234.      */
  235.     public function suggest(string $prefix, array $fieldPaths = [], int $limit 10Criterion $filter null)
  236.     {
  237.     }
  238.     /**
  239.      * Finds Locations for the given query.
  240.      *
  241.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if query is not valid
  242.      *
  243.      * @param \eZ\Publish\API\Repository\Values\Content\LocationQuery $query
  244.      * @param array $languageFilter Configuration for specifying prioritized languages query will be performed on.
  245.      *        Currently supports: <code>array("languages" => array(<language1>,..), "useAlwaysAvailable" => bool)</code>
  246.      *                            useAlwaysAvailable defaults to true to avoid exceptions on missing translations
  247.      * @param bool $filterOnUserPermissions if true only the objects which is the user allowed to read are returned.
  248.      *
  249.      * @return \eZ\Publish\API\Repository\Values\Content\Search\SearchResult
  250.      */
  251.     public function findLocations(LocationQuery $query, array $languageFilter = [], bool $filterOnUserPermissions true): SearchResult
  252.     {
  253.         if (!is_int($query->offset)) {
  254.             throw new InvalidArgumentType(
  255.                 '$query->offset',
  256.                 'integer',
  257.                 $query->offset
  258.             );
  259.         }
  260.         if (!is_int($query->limit)) {
  261.             throw new InvalidArgumentType(
  262.                 '$query->limit',
  263.                 'integer',
  264.                 $query->limit
  265.             );
  266.         }
  267.         $query = clone $query;
  268.         $query->filter $query->filter ?: new Criterion\MatchAll();
  269.         if ($filterOnUserPermissions && !$this->addPermissionsCriterion($query->filter)) {
  270.             return new SearchResult(['time' => 0'totalCount' => 0]);
  271.         }
  272.         $result $this->searchHandler->findLocations($query$languageFilter);
  273.         $missingLocations $this->contentDomainMapper->buildLocationDomainObjectsOnSearchResult($result$languageFilter);
  274.         foreach ($missingLocations as $missingLocation) {
  275.             $this->backgroundIndexer->registerLocation($missingLocation);
  276.         }
  277.         return $result;
  278.     }
  279.     /**
  280.      * Adds content, read Permission criteria if needed and return false if no access at all.
  281.      *
  282.      * @uses \eZ\Publish\API\Repository\PermissionCriterionResolver::getPermissionsCriterion()
  283.      *
  284.      * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $criterion
  285.      *
  286.      * @return bool|\eZ\Publish\API\Repository\Values\Content\Query\Criterion
  287.      */
  288.     protected function addPermissionsCriterion(Criterion &$criterion)
  289.     {
  290.         $permissionCriterion $this->permissionCriterionResolver->getPermissionsCriterion('content''read');
  291.         if ($permissionCriterion === true || $permissionCriterion === false) {
  292.             return $permissionCriterion;
  293.         }
  294.         // Merge with original $criterion
  295.         if ($criterion instanceof LogicalAnd) {
  296.             $criterion->criteria[] = $permissionCriterion;
  297.         } else {
  298.             $criterion = new LogicalAnd(
  299.                 [
  300.                     $criterion,
  301.                     $permissionCriterion,
  302.                 ]
  303.             );
  304.         }
  305.         return true;
  306.     }
  307.     public function supports(int $capabilityFlag): bool
  308.     {
  309.         if ($this->searchHandler instanceof Capable) {
  310.             return $this->searchHandler->supports($capabilityFlag);
  311.         }
  312.         return false;
  313.     }
  314. }