diff --git a/src/Backend/BackendPluginManager.php b/src/Backend/BackendPluginManager.php index c498cc4..d1cdab2 100644 --- a/src/Backend/BackendPluginManager.php +++ b/src/Backend/BackendPluginManager.php @@ -5,6 +5,8 @@ namespace Drupal\search_api\Backend; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\search_api\Event\BackendInfo; +use Drupal\search_api\Annotation\SearchApiBackend; /** * Manages search backend plugins. @@ -28,9 +30,18 @@ class BackendPluginManager extends DefaultPluginManager { * The module handler. */ public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { - parent::__construct('Plugin/search_api/backend', $namespaces, $module_handler, 'Drupal\search_api\Backend\BackendInterface', 'Drupal\search_api\Annotation\SearchApiBackend'); + parent::__construct('Plugin/search_api/backend', $namespaces, $module_handler, BackendInterface::class, SearchApiBackend::class); $this->setCacheBackend($cache_backend, 'search_api_backends'); - $this->alterInfo('search_api_backend_info'); + } + + /** + * {@inheritdoc} + */ + protected function alterDefinitions(&$definitions) { + $this->moduleHandler->alterDeprecated('Use the BackendInfo event instead.', 'search_api_backend_info', $definitions); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(BackendInfo::NAME, new BackendInfo($definitions)); } } diff --git a/src/Datasource/DatasourcePluginManager.php b/src/Datasource/DatasourcePluginManager.php index be9e684..a4b1a50 100644 --- a/src/Datasource/DatasourcePluginManager.php +++ b/src/Datasource/DatasourcePluginManager.php @@ -5,6 +5,7 @@ namespace Drupal\search_api\Datasource; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\search_api\Event\DatasourceInfo; /** * Manages datasource plugins. @@ -30,7 +31,16 @@ class DatasourcePluginManager extends DefaultPluginManager { public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { parent::__construct('Plugin/search_api/datasource', $namespaces, $module_handler, 'Drupal\search_api\Datasource\DatasourceInterface', 'Drupal\search_api\Annotation\SearchApiDatasource'); $this->setCacheBackend($cache_backend, 'search_api_datasources'); - $this->alterInfo('search_api_datasource_info'); + } + + /** + * {@inheritdoc} + */ + protected function alterDefinitions(&$definitions) { + $this->moduleHandler->alterDeprecated('Use the DatasourceInfo event instead.', 'search_api_datasource_info', $definitions); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(DatasourceInfo::NAME, new DatasourceInfo($definitions)); } } diff --git a/src/Entity/Index.php b/src/Entity/Index.php index 7765e07..fb3f186 100644 --- a/src/Entity/Index.php +++ b/src/Entity/Index.php @@ -8,6 +8,8 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\search_api\Datasource\DatasourceInterface; +use Drupal\search_api\Event\ItemsIndexed; +use Drupal\search_api\Event\Reindex; use Drupal\search_api\IndexInterface; use Drupal\search_api\Item\FieldInterface; use Drupal\search_api\LoggerTrait; @@ -20,6 +22,7 @@ use Drupal\search_api\Tracker\TrackerInterface; use Drupal\search_api\Utility\Utility; use Drupal\Core\TempStore\TempStoreException; use Drupal\views\Views; +use Symfony\Component\EventDispatcher\Event; /** * Defines the search index configuration entity. @@ -996,7 +999,13 @@ class Index extends ConfigEntityBase implements IndexInterface { // Since we've indexed items now, triggering reindexing would have some // effect again. Therefore, we reset the flag. $this->setHasReindexed(FALSE); - \Drupal::moduleHandler()->invokeAll('search_api_items_indexed', [$this, $processed_ids]); + + $warning = 'Use the ItemsIndexed event instead'; + \Drupal::moduleHandler()->invokeAllDeprecated($warning, 'search_api_items_indexed', [$this, $processed_ids]); + + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(ItemsIndexed::NAME, new ItemsIndexed($this, $processed_ids)); // Clear search api list caches. Cache::invalidateTags(['search_api_list:' . $this->id]); @@ -1100,7 +1109,10 @@ class Index extends ConfigEntityBase implements IndexInterface { if ($this->status() && !$this->isReindexing()) { $this->setHasReindexed(); $this->getTrackerInstance()->trackAllItemsUpdated(); - \Drupal::moduleHandler()->invokeAll('search_api_index_reindex', [$this, FALSE]); + \Drupal::moduleHandler()->invokeAllDeprecated('Use the Reindex event instead.','search_api_index_reindex', [$this, FALSE]); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(Reindex::NAME, new Reindex($this, FALSE)); } } @@ -1125,7 +1137,11 @@ class Index extends ConfigEntityBase implements IndexInterface { } if ($invoke_hook) { \Drupal::moduleHandler() - ->invokeAll('search_api_index_reindex', [$this, !$this->isReadOnly()]); + ->invokeAllDeprecated('Use the Reindex event instead.', 'search_api_index_reindex', [$this, !$this->isReadOnly()]); + + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(Reindex::NAME, new Reindex($this, !$this->isReadOnly())); } } @@ -1143,7 +1159,10 @@ class Index extends ConfigEntityBase implements IndexInterface { $index_task_manager->startTracking($this); $this->setHasReindexed(); \Drupal::moduleHandler() - ->invokeAll('search_api_index_reindex', [$this, FALSE]); + ->invokeAllDeprecated('Use the Reindex event instead.', 'search_api_index_reindex', [$this, FALSE]); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(Reindex::NAME, new Reindex($this, FALSE)); $index_task_manager->addItemsBatch($this); } diff --git a/src/Event/BackendInfo.php b/src/Event/BackendInfo.php new file mode 100644 index 0000000..800d0cf --- /dev/null +++ b/src/Event/BackendInfo.php @@ -0,0 +1,41 @@ +backendInfo = &$backendInfo; + } + + /** + * Gets the Search API backend info array. + * + * @return array + * Reference to the Search API backend info array, keyed by backend ID. + */ + public function &getBackendInfo() { + return $this->backendInfo; + } + +} diff --git a/src/Event/DatasourceInfo.php b/src/Event/DatasourceInfo.php new file mode 100644 index 0000000..889200c --- /dev/null +++ b/src/Event/DatasourceInfo.php @@ -0,0 +1,39 @@ +infos = &$infos; + } + + /** + * Get a reference to the datasource infos. + * + * @return mixed + */ + public function &getInfos() { + return $this->infos; + } + +} \ No newline at end of file diff --git a/src/Event/ItemsIndexed.php b/src/Event/ItemsIndexed.php new file mode 100644 index 0000000..3eca00c --- /dev/null +++ b/src/Event/ItemsIndexed.php @@ -0,0 +1,60 @@ +index = $index; + $this->processedIds = $processedIds; + } + + /** + * Get the index that indexed the items. + * + * @return \Drupal\search_api\IndexInterface + */ + public function getIndex() { + return $this->index; + } + + /** + * Get the processed ids. + * + * @return int[] + */ + public function getProcessedIds() { + return $this->processedIds; + } + +} diff --git a/src/Event/Reindex.php b/src/Event/Reindex.php new file mode 100644 index 0000000..3289951 --- /dev/null +++ b/src/Event/Reindex.php @@ -0,0 +1,60 @@ +index = $index; + $this->clear = $clear; + } + + /** + * Get the index scheduled for reindexing. + * + * @return \Drupal\search_api\IndexInterface + */ + public function getIndex() { + return $this->index; + } + + /** + * Get the boolean indicating whether the index was also cleared. + * + * @return bool + */ + public function isClear() { + return $this->clear; + } + +} \ No newline at end of file diff --git a/src/Utility/CommandHelper.php b/src/Utility/CommandHelper.php index 86b68f1..e53818b 100644 --- a/src/Utility/CommandHelper.php +++ b/src/Utility/CommandHelper.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\search_api\ConsoleException; +use Drupal\search_api\Event\Reindex; use Drupal\search_api\IndexBatchHelper; use Drupal\search_api\IndexInterface; use Drupal\search_api\SearchApiException; @@ -354,7 +355,10 @@ class CommandHelper implements LoggerAwareInterface { $reindexed_datasources[] = $datasource->label(); } } - $this->moduleHandler->invokeAll('search_api_index_reindex', [$index, FALSE]); + $this->moduleHandler->invokeAllDeprecated('Use the Reindex event instead.', 'search_api_index_reindex', [$index, FALSE]); + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher */ + $dispatcher = \Drupal::getContainer()->get('event_dispatcher'); + $dispatcher->dispatch(Reindex::NAME, new Reindex($index, FALSE)); $arguments = [ '!index' => $index->label(), '!datasources' => implode(', ', $reindexed_datasources), diff --git a/tests/search_api_test_events/search_api_test_events.info.yml b/tests/search_api_test_events/search_api_test_events.info.yml new file mode 100644 index 0000000..2bf1bcc --- /dev/null +++ b/tests/search_api_test_events/search_api_test_events.info.yml @@ -0,0 +1,8 @@ +name: 'Search API Events Test' +type: module +description: 'Support module for Search API tests, tests all the events.' +package: Testing +dependencies: + - search_api:search_api +core: 8.x +hidden: true diff --git a/tests/search_api_test_events/search_api_test_events.services.yml b/tests/search_api_test_events/search_api_test_events.services.yml new file mode 100644 index 0000000..9ac7d9b --- /dev/null +++ b/tests/search_api_test_events/search_api_test_events.services.yml @@ -0,0 +1,7 @@ +services: + + search_api_test_events.event_listener: + class: \Drupal\search_api_test_events\EventListener + arguments: ['@messenger'] + tags: + - { name: event_subscriber } diff --git a/tests/search_api_test_events/src/EventListener.php b/tests/search_api_test_events/src/EventListener.php new file mode 100644 index 0000000..2ef0311 --- /dev/null +++ b/tests/search_api_test_events/src/EventListener.php @@ -0,0 +1,92 @@ +messenger = $messenger; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() { + return [ + BackendInfo::NAME => 'backendInfo', + DatasourceInfo::NAME => 'datasourceInfo', + ItemsIndexed::NAME => 'itemsIndexed', + Reindex::NAME => 'reindex', + ]; + } + + /** + * Event handler for the backend info event. + * + * @param \Drupal\search_api\Event\BackendInfo $event + * The backend info event. + */ + public function backendInfo(BackendInfo $event) { + $backend_info = &$event->getBackendInfo(); + $backend_info['search_api_test']['label'] = 'Slims return'; + } + + /** + * Event handler for the datasource info event. + * + * @param \Drupal\search_api\Event\DatasourceInfo $event + * The datasource info event. + */ + public function datasourceInfo(DatasourceInfo $event) { + $infos = &$event->getInfos(); + if (isset($infos['entity:node'])) { + $infos['entity:node']['label'] = 'Distant land'; + } + } + + /** + * Event handler for the items indexed event. + * + * @param \Drupal\search_api\Event\ItemsIndexed $event + * The items indexed event. + */ + public function itemsIndexed(ItemsIndexed $event) { + $this->messenger->addStatus('Please set me at ease'); + } + + /** + * Event handler for the reindex event. + * + * @param \Drupal\search_api\Event\Reindex $event + * The reindex index event. + */ + public function reindex(Reindex $event) { + $this->messenger->addStatus('Montara'); + } + +} diff --git a/tests/src/Functional/EventsTest.php b/tests/src/Functional/EventsTest.php new file mode 100644 index 0000000..52356ba --- /dev/null +++ b/tests/src/Functional/EventsTest.php @@ -0,0 +1,165 @@ +drupalCreateNode(['type' => 'page', 'title' => 'node - 1']); + $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 2']); + $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 3']); + $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 4']); + + // Create an index and server to work with. + $this->server = $this->getTestServer(); + $index = $this->getTestIndex(); + + // Add the test processor to the index so we can make sure that all expected + // processor methods are called, too. + /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */ + $processor = \Drupal::getContainer() + ->get('search_api.plugin_helper') + ->createProcessorPlugin($index, 'search_api_test'); + $index->addProcessor($processor)->save(); + + // Parts of this test actually use the "database_search_index" from the + // search_api_test_db module (via the test view). Set the processor there, + // too. + $index = Index::load('database_search_index'); + $processor = \Drupal::getContainer() + ->get('search_api.plugin_helper') + ->createProcessorPlugin($index, 'search_api_test'); + $index->addProcessor($processor)->save(); + + // Reset the called methods on the processor. + $this->getCalledMethods('processor'); + + // Log in, so we can test all the things. + $this->drupalLogin($this->adminUser); + } + + /** + * Tests various operations via the Search API's admin UI. + */ + public function testEvents() { + // The BackendInfo event was invoked. + $this->drupalGet('admin/config/search/search-api/add-server'); + $this->assertSession()->pageTextContains('Slims return'); + + // The DatasourceInfo event was invoked. + $this->drupalGet('admin/config/search/search-api/add-index'); + $this->assertSession()->pageTextContains('Distant land'); + // hook_search_api_tracker_info_alter() was invoked. +// $this->assertSession()->pageTextContains('Good luck'); + + // hook_search_api_processor_info_alter() was invoked. + $this->drupalGet($this->getIndexPath('processors')); +// $this->assertSession()->pageTextContains('Mystic bounce'); + + // hook_search_api_parse_mode_info_alter was invoked. + $definition = \Drupal::getContainer() + ->get('plugin.manager.search_api.parse_mode') + ->getDefinition('direct'); +// $this->assertEquals('Song for My Father', $definition['label']); + + // Saving the index should trigger the processor's preIndexSave() method. + $this->submitForm([], 'Save'); + $processor_methods = $this->getCalledMethods('processor'); + $this->assertEquals(['preIndexSave'], $processor_methods); + + $this->drupalGet($this->getIndexPath()); + // Duplication on value 'Index now' with summary. + $this->submitForm([], 'Index now'); + $this->checkForMetaRefresh(); + $this->assertSession()->pageTextContains('Successfully indexed 4 items.'); + + // During indexing, alterIndexedItems() and preprocessIndexItems() should be + // called on the processor. + $processor_methods = $this->getCalledMethods('processor'); + $expected = ['alterIndexedItems', 'preprocessIndexItems']; + $this->assertEquals($expected, $processor_methods); + + // hook_search_api_index_items_alter() was invoked, this removed node:1. + // hook_search_api_query_TAG_alter() was invoked, this removed node:3. +// $this->assertSession()->pageTextContains('There are 2 items indexed on the server for this index.'); +// $this->assertSession()->pageTextContains('Stormy'); + + // The ItemsIndexed event was invoked. + $this->assertSession()->pageTextContains('Please set me at ease'); + + // The Reindex event was invoked. + $this->drupalGet($this->getIndexPath('reindex')); + $this->submitForm([], 'Confirm'); + $this->assertSession()->pageTextContains('Montara'); + + return; // @todo implement other events + // hook_search_api_data_type_info_alter() was invoked. + $this->drupalGet($this->getIndexPath('fields')); + $this->assertSession()->pageTextContains('Peace/Dolphin dance'); + // The implementation of hook_search_api_field_type_mapping_alter() has + // removed all dates, so we can't see any timestamp anymore in the page. + $url_options['query']['datasource'] = 'entity:node'; + $this->drupalGet($this->getIndexPath('fields/add/nojs'), $url_options); + $this->assertSession()->pageTextContains('Add fields to index'); + $this->assertSession()->pageTextNotContains('timestamp'); + + $this->drupalGet('search-api-test'); + $this->assertSession()->pageTextContains('Search id: views_page:search_api_test_view__page_1'); + // hook_search_api_query_alter() was invoked. + $this->assertSession()->pageTextContains('Funky blue note'); + // hook_search_api_results_alter() was invoked. + $this->assertSession()->pageTextContains('Stepping into tomorrow'); + // hook_search_api_results_TAG_alter() was invoked. + $this->assertSession()->pageTextContains('Llama'); + + // The query alter methods of the processor were called. + $processor_methods = $this->getCalledMethods('processor'); + $expected = ['preprocessSearchQuery', 'postprocessSearchResults']; + $this->assertEquals($expected, $processor_methods); + + // hook_search_api_server_features_alter() is triggered. + $this->assertTrue($this->server->supportsFeature('welcome_to_the_jungle')); + + $displays = \Drupal::getContainer()->get('plugin.manager.search_api.display') + ->getInstances(); + // hook_search_api_displays_alter was invoked. + $display_label = $displays['views_page:search_api_test_view__page_1']->label(); + $this->assertEquals('Some funny label for testing', $display_label); + } + +}