vendor/ibexa/elasticsearch/src/lib/Query/EventSubscriber/LanguageQueryFilter.php line 35

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 Ibexa\Elasticsearch\Query\EventSubscriber;
  8. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
  9. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\CustomField;
  10. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LogicalAnd;
  11. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LogicalNot;
  12. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LogicalOr;
  13. use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator;
  14. use Ibexa\Contracts\Elasticsearch\Query\Event\QueryFilterEvent;
  15. use Ibexa\Contracts\Elasticsearch\Query\LanguageFilter;
  16. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  17. final class LanguageQueryFilter implements EventSubscriberInterface
  18. {
  19. private const FIELD_LANGUAGES = 'content_language_codes_ms';
  20. private const FIELD_LANGUAGE = 'meta_indexed_language_code_s';
  21. private const FIELD_IS_MAIN_LANGUAGE = 'meta_indexed_is_main_translation_b';
  22. private const FIELD_IS_ALWAYS_AVAILABLE = 'meta_indexed_is_main_translation_and_always_available_b';
  23. public static function getSubscribedEvents(): array
  24. {
  25. return [
  26. QueryFilterEvent::class => 'onQueryFilterEvent',
  27. ];
  28. }
  29. public function onQueryFilterEvent(QueryFilterEvent $event): void
  30. {
  31. $languageCriteria = $this->createLanguageFilter($event->getLanguageFilter());
  32. // Append language criteria to filter
  33. $query = $event->getQuery();
  34. if ($query->filter === null) {
  35. $query->filter = $languageCriteria;
  36. } else {
  37. $query->filter = new LogicalAnd([
  38. $languageCriteria,
  39. $query->filter,
  40. ]);
  41. }
  42. }
  43. private function createLanguageFilter(LanguageFilter $filter): ?Criterion
  44. {
  45. if (!empty($filter->getLanguages())) {
  46. // Get condition for prioritized languages fallback
  47. $criteria = $this->getLanguagesCriteria($filter->getLanguages());
  48. // Handle always available fallback if used
  49. if ($filter->getUseAlwaysAvailable()) {
  50. $criteria = new LogicalOr([
  51. $criteria,
  52. $this->getAlwaysAvailableCriteria(
  53. $filter->getLanguages(),
  54. $filter->getExcludeTranslationsFromAlwaysAvailable()
  55. ),
  56. ]);
  57. }
  58. return $criteria;
  59. }
  60. // Otherwise search only main languages
  61. return new CustomField(self::FIELD_IS_MAIN_LANGUAGE, Operator::EQ, true);
  62. }
  63. /**
  64. * Returns criteria for prioritized languages fallback.
  65. *
  66. * @param string[] $languageCodes
  67. *
  68. * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion
  69. */
  70. private function getLanguagesCriteria(array $languageCodes): Criterion
  71. {
  72. $criteria = [];
  73. foreach ($languageCodes as $languageCode) {
  74. $criterion = new CustomField(self::FIELD_LANGUAGE, Operator::EQ, $languageCode);
  75. $excluded = $this->getExcludedLanguageCodes($languageCodes, $languageCode);
  76. if (!empty($excluded)) {
  77. $criterion = new LogicalAnd([
  78. $criterion,
  79. new LogicalNot(
  80. new CustomField(self::FIELD_LANGUAGES, Operator::IN, $excluded)
  81. ),
  82. ]);
  83. }
  84. $criteria[] = $criterion;
  85. }
  86. if (count($criteria) > 1) {
  87. return new LogicalOr($criteria);
  88. }
  89. return $criteria[0];
  90. }
  91. /**
  92. * Returns criteria for always available translation fallback.
  93. *
  94. * @param string[] $languageCodes
  95. * @param bool $excludeTranslationsFromAlwaysAvailable
  96. *
  97. * @return \Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion
  98. */
  99. private function getAlwaysAvailableCriteria(
  100. array $languageCodes,
  101. bool $excludeTranslationsFromAlwaysAvailable
  102. ): Criterion {
  103. $excludeOnField = $excludeTranslationsFromAlwaysAvailable
  104. // Exclude all translations by given languages
  105. ? self::FIELD_LANGUAGES
  106. // Exclude only main translation by given languages
  107. : self::FIELD_LANGUAGE
  108. ;
  109. return new LogicalAnd([
  110. // Include always available main language translations
  111. new CustomField(self::FIELD_IS_ALWAYS_AVAILABLE, Operator::EQ, true),
  112. new LogicalNot(
  113. new CustomField($excludeOnField, Operator::IN, $languageCodes)
  114. ),
  115. ]);
  116. }
  117. /**
  118. * Returns a list of language codes to be excluded when matching translation in given
  119. * $selectedLanguageCode.
  120. *
  121. * If $selectedLanguageCode is omitted, all languages will be returned.
  122. *
  123. * @param string[] $languageCodes
  124. * @param string|null $selectedLanguageCode
  125. *
  126. * @return string[]
  127. */
  128. private function getExcludedLanguageCodes(array $languageCodes, ?string $selectedLanguageCode): array
  129. {
  130. $excludedLanguageCodes = [];
  131. foreach ($languageCodes as $languageCode) {
  132. if ($selectedLanguageCode !== null && $languageCode === $selectedLanguageCode) {
  133. break;
  134. }
  135. $excludedLanguageCodes[] = $languageCode;
  136. }
  137. return $excludedLanguageCodes;
  138. }
  139. }
  140. class_alias(LanguageQueryFilter::class, 'Ibexa\Platform\ElasticSearchEngine\Query\EventSubscriber\LanguageQueryFilter');