diff --git a/src/Plugin/search_api/data_type/DateRangeDataType.php b/src/Plugin/search_api/data_type/DateRangeDataType.php new file mode 100644 index 0000000000000000000000000000000000000000..01cc5bcee1970aad51bbb234c50623fd1f224f87 --- /dev/null +++ b/src/Plugin/search_api/data_type/DateRangeDataType.php @@ -0,0 +1,20 @@ +<?php + +namespace Drupal\search_api_opensearch\Plugin\search_api\data_type; + +use Drupal\search_api\Plugin\search_api\data_type\DateDataType; + +/** + * Provides a date range data type. + * + * @SearchApiDataType( + * id = "search_api_opensearch_date_range", + * label = @Translation("Date range"), + * description = @Translation("Date field that contains date ranges."), + * fallback = "date", + * prefix = "dr" + * ) + */ +class DateRangeDataType extends DateDataType { + +} diff --git a/src/Plugin/search_api/processor/DateRange.php b/src/Plugin/search_api/processor/DateRange.php new file mode 100644 index 0000000000000000000000000000000000000000..2537e0141a66ec1498fa1f9ee6241350be6a8b27 --- /dev/null +++ b/src/Plugin/search_api/processor/DateRange.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\search_api_opensearch\Plugin\search_api\processor; + +use Drupal\search_api\Processor\ProcessorPluginBase; + +/** + * Add date ranges to the index. + * + * @SearchApiProcessor( + * id = "search_api_opensearch_date_range", + * label = @Translation("Date ranges"), + * description = @Translation("Date ranges."), + * stages = { + * "preprocess_index" = 0, + * }, + * locked = true, + * hidden = true, + * ) + */ +class DateRange extends ProcessorPluginBase { + + /** + * {@inheritdoc} + */ + public function preprocessIndexItems(array $items) { + foreach ($items as $item) { + foreach ($item->getFields() as $field) { + if ('search_api_opensearch_date_range' == $field->getType()) { + $required_properties = [ + $item->getDatasourceId() => [ + $field->getPropertyPath() . ':value' => 'start', + $field->getPropertyPath() . ':end_value' => 'end', + ], + ]; + $item_values = $this->getFieldsHelper()->extractItemValues([$item], $required_properties); + foreach ($item_values as $dates) { + $start_dates = $dates['start']; + $end_dates = $dates['end']; + for ($i = 0, $n = count($start_dates); $i < $n; $i++) { + $values[$i] = [ + 'gte' => $start_dates[$i], + 'lte' => $end_dates[$i], + ]; + } + } + if (!empty($values)) { + $field->setValues($values); + } + } + } + } + } + +} diff --git a/src/SearchAPI/FieldMapper.php b/src/SearchAPI/FieldMapper.php index 5d8aca3ad8f5affe94ac9ea9e364bec3cd24348b..ef69606a6065b10700bfb42dc092e5c9ab3657c5 100644 --- a/src/SearchAPI/FieldMapper.php +++ b/src/SearchAPI/FieldMapper.php @@ -145,6 +145,10 @@ class FieldMapper { 'type' => 'date', 'format' => 'strict_date_optional_time||epoch_second', ], + 'search_api_opensearch_date_range' => [ + 'type' => 'date_range', + 'format' => 'strict_date_optional_time||epoch_second', + ], 'attachment' => ['type' => 'attachment'], 'object' => ['type' => 'nested'], 'location' => ['type' => 'geo_point'], diff --git a/tests/src/Kernel/DataTypeTest.php b/tests/src/Kernel/DataTypeTest.php index ff735e6775be10fc0f0d71f5f94eff4f268380e5..03b85a24492f03e471ff529ebb0f17b6e1b68ef8 100644 --- a/tests/src/Kernel/DataTypeTest.php +++ b/tests/src/Kernel/DataTypeTest.php @@ -29,4 +29,17 @@ class DataTypeTest extends KernelTestBase { $this->assertEquals('search_api_opensearch', $definition['provider']); } + /** + * Tests the date range type is found. + */ + public function testHasDateRangeDataType() { + /** @var \Drupal\search_api\DataType\DataTypePluginManager $manager */ + $manager = $this->container->get('plugin.manager.search_api.data_type'); + $definitions = $manager->getDefinitions(); + $this->assertArrayHasKey('search_api_opensearch_date_range', $definitions); + $definition = $definitions['search_api_opensearch_date_range']; + $this->assertEquals('search_api_opensearch_date_range', $definition['id']); + $this->assertEquals('search_api_opensearch', $definition['provider']); + } + } diff --git a/tests/src/Unit/Plugin/processor/DateRangeTest.php b/tests/src/Unit/Plugin/processor/DateRangeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1175fbddfc850bc0a4c86f9cf9fbc27b774ca1c3 --- /dev/null +++ b/tests/src/Unit/Plugin/processor/DateRangeTest.php @@ -0,0 +1,128 @@ +<?php + +namespace Drupal\Tests\search_api_opensearch\Unit\Plugin\processor; + +use Drupal\search_api\Datasource\DatasourceInterface; +use Drupal\search_api\IndexInterface; +use Drupal\search_api\Item\Field; +use Drupal\search_api\Item\Item; +use Drupal\search_api\Utility\FieldsHelper; +use Drupal\search_api_opensearch\Plugin\search_api\processor\DateRange; +use Drupal\Tests\UnitTestCase; + +/** + * Tests the date range processor. + * + * @coversDefaultClass \Drupal\search_api_opensearch\Plugin\search_api\processor\DateRange + * @group search_api_opensearch + */ +class DateRangeTest extends UnitTestCase { + + /** + * Test making date range field types compatible with OS. + * + * @covers ::preprocessIndexItems + * @dataProvider dateValueDataProvider + */ + public function testPreProcessingDateRangeItems( + array $extractItemResult, + array $expectedResult + ): void { + + $dataSource = $this->prophesize(DatasourceInterface::class); + $dataSource->getPluginId()->willReturn('entity:node'); + + $index = $this->prophesize(IndexInterface::class); + $indexId = "index_" . $this->randomMachineName(); + $index->id()->willReturn($indexId); + + $itemId = "item1_" . $this->randomMachineName(); + + $item = (new Item($index->reveal(), $itemId, $dataSource->reveal())) + ->setFieldsExtracted(TRUE) + ->setField( + 'event_date_range', + (new Field($index->reveal(), 'event_date_range')) + ->setType("search_api_opensearch_date_range") + ->setPropertyPath('field_event_instance') + ); + + $fieldsHelper = $this->prophesize(FieldsHelper::class); + $fieldsHelper->extractItemValues([$item], [ + 'entity:node' => [ + 'field_event_instance:value' => 'start', + 'field_event_instance:end_value' => 'end', + ], + ])->willReturn($extractItemResult); + + $dateRange = new DateRange([], 'search_api_opensearch_date_range', []); + $dateRange->setFieldsHelper($fieldsHelper->reveal()); + + $dateRange->preprocessIndexItems([$item]); + + $fieldValues = $item->getField('event_date_range')->getValues(); + + $this->assertEquals($expectedResult, $fieldValues); + + } + + /** + * Provides dummy date range data. + */ + public function dateValueDataProvider(): array { + return [ + 'Single date range' => [ + [ + [ + 'start' => [ + '2022-07-01T09:30:00', + ], + 'end' => [ + '2022-07-01T13:00:00', + ], + ], + ], + [ + [ + 'gte' => '2022-07-01T09:30:00', + 'lte' => '2022-07-01T13:00:00', + ], + ], + ], + 'Multiple date ranges' => [ + [ + [ + 'start' => [ + '2022-07-01T09:30:00', + '2022-08-05T09:30:00', + ], + 'end' => [ + '2022-07-01T13:00:00', + '2022-08-05T13:00:00', + ], + ], + ], + [ + [ + 'gte' => '2022-07-01T09:30:00', + 'lte' => '2022-07-01T13:00:00', + ], + [ + 'gte' => '2022-08-05T09:30:00', + 'lte' => '2022-08-05T13:00:00', + ], + ], + ], + 'No date ranges' => [ + [ + [ + 'start' => [], + 'end' => [], + ], + ], + [], + ], + ]; + } + +}