diff --git a/composer.json b/composer.json index c35de4f9..37b1e1e5 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ }, "require-dev": { "symfony/proxy-manager-bridge": "^5.4", + "symfony/phpunit-bridge": "^5.4", "ibexa/doctrine-schema": "~4.6.0@dev", "phpunit/phpunit": "^8.2", "matthiasnoback/symfony-dependency-injection-test": "^4.1", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b7d85079..030590e9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2249,8 +2249,3 @@ parameters: message: "#^Parameter \\#1 \\$contentObjects of method Ibexa\\\\Solr\\\\Handler\\:\\:bulkIndexContent\\(\\) expects array\\, iterable\\ given\\.$#" count: 1 path: tests/lib/SetupFactory/LegacySetupFactory.php - - - - message: "#^Parameter \\#1 \\$paths of class Symfony\\\\Component\\\\Config\\\\FileLocator constructor expects array\\\\|string, string\\|false given\\.$#" - count: 2 - path: tests/lib/SetupFactory/LegacySetupFactory.php diff --git a/src/contracts/Test/IbexaSolrTestKernel.php b/src/contracts/Test/IbexaSolrTestKernel.php new file mode 100644 index 00000000..6c05ab83 --- /dev/null +++ b/src/contracts/Test/IbexaSolrTestKernel.php @@ -0,0 +1,48 @@ + Handler::class; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + parent::registerContainerConfiguration($loader); + + $loader->load(static function (ContainerBuilder $container): void { + (new SolrTestContainerBuilder())->loadSolrSettings($container); + }); + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/AbstractImageRangeVisitor.php b/src/lib/Query/Image/CriterionVisitor/AbstractImageRangeVisitor.php new file mode 100644 index 00000000..46dca9c3 --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/AbstractImageRangeVisitor.php @@ -0,0 +1,32 @@ +value; + $queries = []; + + foreach ($this->getSearchFieldNames($criterion) as $fieldName) { + $queries[] = $fieldName . ':' . $this->getRange( + $criterion->operator, + $criterionValue[0], + $criterionValue[1] ?? null + ); + } + + return '(' . implode(' OR ', $queries) . ')'; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/AbstractImageTermsVisitor.php b/src/lib/Query/Image/CriterionVisitor/AbstractImageTermsVisitor.php new file mode 100644 index 00000000..51167542 --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/AbstractImageTermsVisitor.php @@ -0,0 +1,36 @@ +|string $criterionValue */ + $criterionValue = $criterion->value; + + foreach ($this->getSearchFieldNames($criterion) as $fieldName) { + if (is_array($criterionValue)) { + foreach ($criterionValue as $value) { + $queries[] = $fieldName . ':' . $value; + } + } + + if (is_string($criterionValue)) { + $queries[] = $fieldName . ':' . $criterionValue; + } + } + + return '(' . implode(' OR ', $queries) . ')'; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/AbstractImageVisitor.php b/src/lib/Query/Image/CriterionVisitor/AbstractImageVisitor.php new file mode 100644 index 00000000..9cd613ec --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/AbstractImageVisitor.php @@ -0,0 +1,58 @@ +fieldNameResolver = $fieldNameResolver; + $this->imageFieldType = $imageFieldType; + } + + abstract protected function getSearchFieldName(): string; + + /** + * @return array + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + protected function getSearchFieldNames(Criterion $criterion): array + { + $searchFieldNames = array_keys( + $this->fieldNameResolver->getFieldTypes( + $criterion, + $criterion->target, + $this->imageFieldType->getFieldTypeIdentifier(), + $this->getSearchFieldName() + ) + ); + + if (empty($searchFieldNames)) { + throw new InvalidArgumentException( + '$criterion->target', + "No searchable Fields found for the provided Criterion target '{$criterion->target}'." + ); + } + + return $searchFieldNames; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/FileSize.php b/src/lib/Query/Image/CriterionVisitor/FileSize.php new file mode 100644 index 00000000..9c2c43fb --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/FileSize.php @@ -0,0 +1,31 @@ +operator === Operator::BETWEEN + || $criterion->operator === Operator::GTE + ); + } + + protected function getSearchFieldName(): string + { + return self::SEARCH_FIELD_FILE_SIZE; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/Height.php b/src/lib/Query/Image/CriterionVisitor/Height.php new file mode 100644 index 00000000..87c2cb39 --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/Height.php @@ -0,0 +1,31 @@ +operator === Operator::BETWEEN + || $criterion->operator === Operator::GTE + ); + } + + protected function getSearchFieldName(): string + { + return self::SEARCH_FIELD_HEIGHT; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/MimeType.php b/src/lib/Query/Image/CriterionVisitor/MimeType.php new file mode 100644 index 00000000..dd42384b --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/MimeType.php @@ -0,0 +1,31 @@ +operator === Operator::EQ + || $criterion->operator === Operator::IN + ); + } + + protected function getSearchFieldName(): string + { + return self::SEARCH_FIELD_MIME_TYPE; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/Orientation.php b/src/lib/Query/Image/CriterionVisitor/Orientation.php new file mode 100644 index 00000000..4bec2dbe --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/Orientation.php @@ -0,0 +1,31 @@ +operator === Operator::EQ + || $criterion->operator === Operator::IN + ); + } + + protected function getSearchFieldName(): string + { + return self::SEARCH_FIELD_ORIENTATION; + } +} diff --git a/src/lib/Query/Image/CriterionVisitor/Width.php b/src/lib/Query/Image/CriterionVisitor/Width.php new file mode 100644 index 00000000..18ca6250 --- /dev/null +++ b/src/lib/Query/Image/CriterionVisitor/Width.php @@ -0,0 +1,31 @@ +operator === Operator::BETWEEN + || $criterion->operator === Operator::GTE + ); + } + + protected function getSearchFieldName(): string + { + return self::SEARCH_FIELD_WIDTH; + } +} diff --git a/src/lib/Resources/config/container/solr.yml b/src/lib/Resources/config/container/solr.yml index b8f416c0..ee461403 100644 --- a/src/lib/Resources/config/container/solr.yml +++ b/src/lib/Resources/config/container/solr.yml @@ -61,7 +61,10 @@ services: - '@ibexa.solr.query.content.facet_builder_visitor.aggregate' - '@ibexa.solr.query.content.aggregation_result_extractor.dispatcher' - '@Ibexa\Solr\Gateway\EndpointRegistry' - deprecated: 'The "%service_id%" service is deprecated since eZ Platform 3.2.0, to be removed in eZ Platform 4.0.0., use ezpublish.search.solr.result_extractor.content.native or ezpublish.search.solr.result_extractor.location.native instead.' + deprecated: + version: 'eZ Platform 3.2.0' + package: 'ibexa/solr' + message: 'The "%service_id%" service is deprecated since eZ Platform 3.2.0, to be removed in Ibexa DXP 5.0.0. Use ibexa.solr.result_extractor.content.native or ibexa.solr.result_extractor.location.native instead.' ibexa.solr.result_extractor.content.native: class: Ibexa\Solr\ResultExtractor\NativeResultExtractor @@ -79,7 +82,10 @@ services: ibexa.solr.result_extractor: alias: ibexa.solr.result_extractor.native - deprecated: 'The "%alias_id%" alias is deprecated since eZ Platform 3.2.0, to be removed in eZ Platform 4.0.0. Use ezpublish.search.solr.result_extractor.content or ezpublish.search.solr.result_extractor.location instead' + deprecated: + version: 'eZ Platform 3.2.0' + package: 'ibexa/solr' + message: 'The "%alias_id%" alias is deprecated since eZ Platform 3.2.0, to be removed in Ibexa DXP 5.0.0. Use ibexa.solr.result_extractor.content or ibexa.solr.result_extractor.location instead' ibexa.solr.result_extractor.content: alias: ibexa.solr.result_extractor.content.native diff --git a/src/lib/Resources/config/container/solr/criterion_visitors.yml b/src/lib/Resources/config/container/solr/criterion_visitors.yml index 77ff467c..df8fc881 100644 --- a/src/lib/Resources/config/container/solr/criterion_visitors.yml +++ b/src/lib/Resources/config/container/solr/criterion_visitors.yml @@ -293,3 +293,39 @@ services: tags: - {name: ibexa.search.solr.query.content.criterion.visitor} - {name: ibexa.search.solr.query.location.criterion.visitor} + + Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor: + abstract: true + arguments: + $fieldNameResolver: '@Ibexa\Core\Search\Common\FieldNameResolver' + $imageFieldType: '@Ibexa\Core\FieldType\Image\Type' + + Ibexa\Solr\Query\Image\CriterionVisitor\MimeType: + parent: Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor + tags: + - { name: ibexa.search.solr.query.content.criterion.visitor } + - { name: ibexa.search.solr.query.location.criterion.visitor } + + Ibexa\Solr\Query\Image\CriterionVisitor\FileSize: + parent: Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor + tags: + - { name: ibexa.search.solr.query.content.criterion.visitor } + - { name: ibexa.search.solr.query.location.criterion.visitor } + + Ibexa\Solr\Query\Image\CriterionVisitor\Width: + parent: Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor + tags: + - { name: ibexa.search.solr.query.content.criterion.visitor } + - { name: ibexa.search.solr.query.location.criterion.visitor } + + Ibexa\Solr\Query\Image\CriterionVisitor\Height: + parent: Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor + tags: + - { name: ibexa.search.solr.query.content.criterion.visitor } + - { name: ibexa.search.solr.query.location.criterion.visitor } + + Ibexa\Solr\Query\Image\CriterionVisitor\Orientation: + parent: Ibexa\Solr\Query\Image\CriterionVisitor\AbstractImageVisitor + tags: + - { name: ibexa.search.solr.query.content.criterion.visitor } + - { name: ibexa.search.solr.query.location.criterion.visitor } diff --git a/src/lib/Test/SolrTestContainerBuilder.php b/src/lib/Test/SolrTestContainerBuilder.php new file mode 100644 index 00000000..928e3f7b --- /dev/null +++ b/src/lib/Test/SolrTestContainerBuilder.php @@ -0,0 +1,60 @@ + 'multicore_dedicated.yml', + SearchServiceTranslationLanguageFallbackTest::SETUP_SHARED => 'multicore_shared.yml', + SearchServiceTranslationLanguageFallbackTest::SETUP_SINGLE => 'single_core.yml', + SearchServiceTranslationLanguageFallbackTest::SETUP_CLOUD => 'cloud.yml', + ]; + + public function loadSolrSettings(ContainerBuilder $containerBuilder): void + { + $containerBuilder->setParameter('test.ibexa.solr.host', getenv('SOLR_HOST') ?: 'localhost'); + + $settingsPath = dirname(__DIR__) . '/Resources/config/container/'; + $testSettingsPath = dirname(__DIR__, 3) . '/tests/lib/Resources/config/'; + + $solrLoader = new YamlFileLoader($containerBuilder, new FileLocator($settingsPath)); + $solrLoader->load('solr.yml'); + + $testConfigurationFile = $this->getTestConfigurationFile(); + $solrTestLoader = new YamlFileLoader($containerBuilder, new FileLocator($testSettingsPath)); + $solrTestLoader->load($testConfigurationFile); + + $containerBuilder->addResource(new FileResource($testSettingsPath . $testConfigurationFile)); + } + + public function getTestConfigurationFile(): string + { + $isSolrCloud = getenv('SOLR_CLOUD') === 'yes'; + $coresSetup = $isSolrCloud + ? SearchServiceTranslationLanguageFallbackTest::SETUP_CLOUD + : getenv('CORES_SETUP'); + + if (!isset(self::CONFIGURATION_FILES_MAP[$coresSetup])) { + throw new RuntimeException("Backend cores setup '{$coresSetup}' is not handled"); + } + + return self::CONFIGURATION_FILES_MAP[$coresSetup]; + } +} diff --git a/tests/lib/SetupFactory/LegacySetupFactory.php b/tests/lib/SetupFactory/LegacySetupFactory.php index 54804b23..cece8115 100644 --- a/tests/lib/SetupFactory/LegacySetupFactory.php +++ b/tests/lib/SetupFactory/LegacySetupFactory.php @@ -18,12 +18,9 @@ use Ibexa\Solr\Container\Compiler; use Ibexa\Solr\Gateway\UpdateSerializerInterface; use Ibexa\Solr\Handler as SolrSearchHandler; -use Ibexa\Tests\Integration\Core\Repository\SearchServiceTranslationLanguageFallbackTest; -use RuntimeException; -use Symfony\Component\Config\FileLocator; +use Ibexa\Solr\Test\SolrTestContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -33,12 +30,16 @@ */ class LegacySetupFactory extends CoreLegacySetupFactory { - public const CONFIGURATION_FILES_MAP = [ - SearchServiceTranslationLanguageFallbackTest::SETUP_DEDICATED => 'multicore_dedicated.yml', - SearchServiceTranslationLanguageFallbackTest::SETUP_SHARED => 'multicore_shared.yml', - SearchServiceTranslationLanguageFallbackTest::SETUP_SINGLE => 'single_core.yml', - SearchServiceTranslationLanguageFallbackTest::SETUP_CLOUD => 'cloud.yml', - ]; + public const CONFIGURATION_FILES_MAP = SolrTestContainerBuilder::CONFIGURATION_FILES_MAP; + + private SolrTestContainerBuilder $containerBuilder; + + public function __construct() + { + parent::__construct(); + + $this->containerBuilder = new SolrTestContainerBuilder(); + } /** * Returns a configured repository for testing. @@ -71,16 +72,7 @@ protected function externalBuildContainer(ContainerBuilder $containerBuilder): v protected function loadSolrSettings(ContainerBuilder $containerBuilder): void { - $containerBuilder->setParameter('test.ibexa.solr.host', getenv('SOLR_HOST') ?: 'localhost'); - - $settingsPath = realpath(__DIR__ . '/../../../src/lib/Resources/config/container/'); - $testSettingsPath = realpath(__DIR__ . '/../Resources/config/'); - - $solrLoader = new YamlFileLoader($containerBuilder, new FileLocator($settingsPath)); - $solrLoader->load('solr.yml'); - - $solrTestLoader = new YamlFileLoader($containerBuilder, new FileLocator($testSettingsPath)); - $solrTestLoader->load($this->getTestConfigurationFile()); + $this->containerBuilder->loadSolrSettings($containerBuilder); $containerBuilder->addCompilerPass(new Compiler\FieldMapperPass\BlockFieldMapperPass()); $containerBuilder->addCompilerPass(new Compiler\FieldMapperPass\BlockTranslationFieldMapperPass()); @@ -152,16 +144,7 @@ protected function indexAll(): void protected function getTestConfigurationFile(): string { - $isSolrCloud = getenv('SOLR_CLOUD') === 'yes'; - $coresSetup = $isSolrCloud - ? SearchServiceTranslationLanguageFallbackTest::SETUP_CLOUD - : getenv('CORES_SETUP'); - - if (!isset(self::CONFIGURATION_FILES_MAP[$coresSetup])) { - throw new RuntimeException("Backend cores setup '{$coresSetup}' is not handled"); - } - - return self::CONFIGURATION_FILES_MAP[$coresSetup]; + return $this->containerBuilder->getTestConfigurationFile(); } private function configureSymfonyHttpClient(ContainerBuilder $containerBuilder): void