diff --git a/config/schema/search_api.datasource.schema.yml b/config/schema/search_api.datasource.schema.yml index e98979d..fc0f4a2 100644 --- a/config/schema/search_api.datasource.schema.yml +++ b/config/schema/search_api.datasource.schema.yml @@ -3,7 +3,7 @@ label: 'Entity datasource configuration' mapping: default: - type: string + type: integer label: 'Decides if we want to include or exclude the selected bundles.' bundles: type: sequence diff --git a/config/schema/search_api.index.schema.yml b/config/schema/search_api.index.schema.yml index 427f03f..3af6a34 100644 --- a/config/schema/search_api.index.schema.yml +++ b/config/schema/search_api.index.schema.yml @@ -17,7 +17,7 @@ search_api.index.*: read_only: type: boolean label: 'Read-only' - fields: + field_settings: type: sequence label: 'Indexed fields' sequence: @@ -48,24 +48,18 @@ search_api.index.*: hidden: type: boolean label: 'Whether the field should appear in the UI' - processors: + processor_settings: type: sequence label: 'Processor settings' sequence: type: mapping label: 'A processor' mapping: - processor_id: + plugin_id: type: string label: 'The plugin ID of the processor' - weights: - type: sequence - label: 'The processor''s weights for the different processing stages' - sequence: - type: integer - label: 'The processor''s weight for this stage' settings: - type: plugin.plugin_configuration.search_api_processor.[%parent.processor_id] + type: plugin.plugin_configuration.search_api_processor.[%parent.plugin_id] options: type: mapping label: 'Options' @@ -76,23 +70,30 @@ search_api.index.*: index_directly: type: boolean label: 'Index items immediately' - datasources: + datasource_settings: type: sequence - label: 'Datasource plugin IDs' + label: 'Datasource settings' sequence: - type: string - datasource_configs: + type: mapping + label: 'A datasource' + mapping: + plugin_id: + type: string + label: 'The plugin ID of the datasource config' + settings: + type: plugin.plugin_configuration.search_api_datasource.[%parent.plugin_id] + tracker_settings: type: sequence - label: 'Datasource plugin configurations' + label: 'Tracker settings' sequence: - type: plugin.plugin_configuration.search_api_datasource.[%key] - label: 'Datasource plugin configuration' - tracker: - type: string - label: 'Tracker plugin ID' - tracker_config: - label: 'Tracker config plugin' - type: plugin.plugin_configuration.search_api_tracker.[%parent.tracker] + type: mapping + label: 'A tracker' + mapping: + plugin_id: + type: string + label: 'The plugin ID of the tracker config' + settings: + type: plugin.plugin_configuration.search_api_tracker.[%parent.plugin_id] server: type: string label: 'Server ID' diff --git a/config/schema/search_api.processor.schema.yml b/config/schema/search_api.processor.schema.yml index 5366cb1..1448267 100644 --- a/config/schema/search_api.processor.schema.yml +++ b/config/schema/search_api.processor.schema.yml @@ -2,6 +2,12 @@ plugin.plugin_configuration.search_api_processor.add_url: type: mapping label: 'Add URL configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -13,6 +19,12 @@ plugin.plugin_configuration.search_api_processor.aggregated_field: type: mapping label: 'Add aggregation processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The aggregated fields configured for this index' @@ -37,6 +49,12 @@ plugin.plugin_configuration.search_api_processor.highlight: type: mapping label: 'Highlight processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' prefix: type: string label: 'Text/HTML that will be prepended to all occurrences of search keywords in highlighted text' @@ -57,6 +75,12 @@ plugin.plugin_configuration.search_api_processor.html_filter: type: mapping label: 'HTML filter processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -80,6 +104,12 @@ plugin.plugin_configuration.search_api_processor.ignorecase: type: mapping label: 'Ignore case processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -91,6 +121,12 @@ plugin.plugin_configuration.search_api_processor.ignore_character: type: mapping label: 'Ignore Character processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -115,6 +151,12 @@ plugin.plugin_configuration.search_api_processor.language: type: mapping label: 'Language field processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -126,6 +168,12 @@ plugin.plugin_configuration.search_api_processor.node_status: type: mapping label: 'node status processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -137,7 +185,12 @@ plugin.plugin_configuration.search_api_processor.rendered_item: type: mapping label: 'Rendered item processor configuration' mapping: - # @todo Verify "sequence" is right here once schemas are really used. + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' view_mode: type: sequence label: 'The selected view modes for each datasource, by bundle' @@ -158,6 +211,12 @@ plugin.plugin_configuration.search_api_processor.role_filter: type: mapping label: 'Role filter processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' default: type: boolean label: 'Default' @@ -172,6 +231,12 @@ plugin.plugin_configuration.search_api_processor.stopwords: type: mapping label: 'Stopwords processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -189,6 +254,12 @@ plugin.plugin_configuration.search_api_processor.tokenizer: type: mapping label: 'Tokenizer processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -212,6 +283,12 @@ plugin.plugin_configuration.search_api_processor.transliteration: type: mapping label: 'Transliteration processor configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' @@ -223,6 +300,12 @@ plugin.plugin_configuration.search_api_processor.content_access: type: mapping label: 'Content Access configuration' mapping: + weights: + type: sequence + label: 'The processor''s weights for the different processing stages' + sequence: + type: integer + label: 'The processor''s weight for this stage' fields: type: sequence label: 'The selected fields' diff --git a/config/schema/search_api.tracker.schema.yml b/config/schema/search_api.tracker.schema.yml index 297badc..1cb3518 100644 --- a/config/schema/search_api.tracker.schema.yml +++ b/config/schema/search_api.tracker.schema.yml @@ -1,4 +1,3 @@ -# @todo Update the key when https://www.drupal.org/node/2291073 is fixed. plugin.plugin_configuration.search_api_tracker.default: type: sequence label: 'Entity tracker configuration' diff --git a/search_api.drush.inc b/search_api.drush.inc index e403668..e2460f7 100644 --- a/search_api.drush.inc +++ b/search_api.drush.inc @@ -216,7 +216,7 @@ function drush_search_api_list() { $rows[] = array( $index->id(), $index->label(), - $index->getServerId() ? $index->getServer()->label() : $none, + $index->getServerId() ? $index->getServerInstance()->label() : $none, $types ? implode(', ', $types) : $none, $index->status() ? $enabled : $disabled, (int) $index->getOption('cron_limit'), @@ -325,8 +325,8 @@ function drush_search_api_status($index_id = NULL) { ); foreach ($indexes as $index) { - $indexed = $index->getTracker()->getIndexedItemsCount(); - $total = $index->getTracker()->getTotalItemsCount(); + $indexed = $index->getTrackerInstance()->getIndexedItemsCount(); + $total = $index->getTrackerInstance()->getTotalItemsCount(); $complete = '-'; if ($total > 0) { @@ -365,7 +365,7 @@ function drush_search_api_index($index_id = NULL, $limit = NULL, $batch_size = N } foreach ($indexes as $index) { - $tracker = $index->getTracker(); + $tracker = $index->getTrackerInstance(); $remaining = $tracker->getTotalItemsCount() - $tracker->getIndexedItemsCount(); if (!$remaining) { diff --git a/search_api.theme.inc b/search_api.theme.inc index 7de7c73..bc42364 100644 --- a/search_api.theme.inc +++ b/search_api.theme.inc @@ -229,8 +229,8 @@ function theme_search_api_index($variables) { // Get the index. /** @var $index \Drupal\search_api\IndexInterface */ $index = $variables['index']; - $server = $index->hasValidServer() ? $index->getServer() : NULL; - $tracker = $index->hasValidTracker() ? $index->getTracker() : NULL; + $server = $index->hasValidServer() ? $index->getServerInstance() : NULL; + $tracker = $index->hasValidTracker() ? $index->getTrackerInstance() : NULL; $output = ''; diff --git a/search_api_db/search_api_db_defaults/config/optional/search_api.index.default_index.yml b/search_api_db/search_api_db_defaults/config/optional/search_api.index.default_index.yml index 7941caa..3cc2de8 100644 --- a/search_api_db/search_api_db_defaults/config/optional/search_api.index.default_index.yml +++ b/search_api_db/search_api_db_defaults/config/optional/search_api.index.default_index.yml @@ -2,7 +2,7 @@ id: default_index name: 'Default content index' description: 'Default content index created by the Database Search Defaults module' read_only: false -fields: +field_settings: search_api_language: label: 'Item language' type: string @@ -70,29 +70,29 @@ fields: type: string datasource_id: 'entity:node' property_path: type -processors: +processor_settings: content_access: - processor_id: content_access - weights: - preprocess_index: -6 - preprocess_query: -4 - settings: { } + plugin_id: content_access + settings: + weights: + preprocess_index: -6 + preprocess_query: -4 highlight: - processor_id: highlight - weights: - postprocess_query: -9 + plugin_id: highlight settings: + weights: + postprocess_query: -9 highlight: always excerpt: true excerpt_length: 256 prefix: '' suffix: '' html_filter: - processor_id: html_filter - weights: - preprocess_index: -3 - preprocess_query: -6 + plugin_id: html_filter settings: + weights: + preprocess_index: -3 + preprocess_query: -6 fields: - rendered_item title: true @@ -104,29 +104,29 @@ processors: string: 2 b: 2 ignorecase: - processor_id: ignorecase - weights: - preprocess_index: -5 - preprocess_query: -8 + plugin_id: ignorecase settings: + weights: + preprocess_index: -5 + preprocess_query: -8 fields: - rendered_item - title language: - processor_id: language - weights: - preprocess_index: -50 - settings: { } + plugin_id: language + settings: + weights: + preprocess_index: -50 node_status: - processor_id: node_status - weights: - preprocess_index: -10 - settings: { } + plugin_id: node_status + settings: + weights: + preprocess_index: -10 rendered_item: - processor_id: rendered_item - weights: - preprocess_index: -8 + plugin_id: rendered_item settings: + weights: + preprocess_index: -8 roles: anonymous: anonymous view_mode: @@ -134,11 +134,11 @@ processors: article: search_index page: search_index stopwords: - processor_id: stopwords - weights: - preprocess_query: -10 - postprocess_query: -10 + plugin_id: stopwords settings: + weights: + preprocess_query: -10 + postprocess_query: -10 fields: - rendered_item - title @@ -179,11 +179,11 @@ processors: - will - with tokenizer: - processor_id: tokenizer - weights: - preprocess_index: -2 - preprocess_query: -5 + plugin_id: tokenizer settings: + weights: + preprocess_index: -2 + preprocess_query: -5 fields: - rendered_item - title @@ -191,27 +191,29 @@ processors: overlap_cjk: 1 minimum_word_size: '3' transliteration: - processor_id: transliteration - weights: - preprocess_index: -4 - preprocess_query: -7 + plugin_id: transliteration settings: + weights: + preprocess_index: -4 + preprocess_query: -7 fields: - rendered_item - title options: index_directly: true cron_limit: 50 -datasources: - - 'entity:node' -datasource_configs: +datasource_settings: 'entity:node': - default: '1' - bundles: - article: '0' - page: '0' -tracker: default -tracker_config: { } + plugin_id: 'entity:node' + settings: + default: 1 + bundles: + article: '0' + page: '0' +tracker_settings: + 'default': + plugin_id: default + settings: { } server: default_server status: true langcode: en diff --git a/search_api_db/src/Plugin/search_api/backend/Database.php b/search_api_db/src/Plugin/search_api/backend/Database.php index ef1af4a..c5fba12 100644 --- a/search_api_db/src/Plugin/search_api/backend/Database.php +++ b/search_api_db/src/Plugin/search_api/backend/Database.php @@ -1287,7 +1287,7 @@ class Database extends BackendPluginBase { if (count($words) > 1 && max(array_map('strlen', $words)) <= 50) { // Overlong token is due to bad tokenizing. // Check for "Tokenizer" preprocessor on index. - if (empty($index->getProcessorSettings()['search_api_tokenizer']['status'])) { + if (empty($index->getProcessors()['tokenizer'])) { $this->getLogger()->warning('An overlong word (more than 50 characters) was encountered while indexing, due to bad tokenizing. It is recommended to enable the "Tokenizer" preprocessor for indexes using database servers. Otherwise, the backend class has to use its own, fixed tokenizing.'); } else { diff --git a/search_api_db/tests/src/Kernel/BackendTest.php b/search_api_db/tests/src/Kernel/BackendTest.php index 5c43111..dcda591 100644 --- a/search_api_db/tests/src/Kernel/BackendTest.php +++ b/search_api_db/tests/src/Kernel/BackendTest.php @@ -157,8 +157,11 @@ class BackendTest extends KernelTestBase { $index = $this->getIndex(); $this->assertTrue((bool) $index, 'The index was successfully created.'); - $this->assertEquals(5, $index->getTracker()->getTotalItemsCount(), 'Correct item count.'); - $this->assertEquals(0, $index->getTracker()->getIndexedItemsCount(), 'All items still need to be indexed.'); + $this->assertEquals(array("entity:entity_test"), $index->getDatasourceIds(), 'Datasources are set correctly.'); + $this->assertEquals('default', $index->getTrackerId(), 'Tracker is set correctly.'); + + $this->assertEquals(5, $index->getTrackerInstance()->getTotalItemsCount(), 'Correct item count.'); + $this->assertEquals(0, $index->getTrackerInstance()->getIndexedItemsCount(), 'All items still need to be indexed.'); } /** @@ -200,14 +203,15 @@ class BackendTest extends KernelTestBase { $property = 'body'; $this->addField($index, $property); - $processors = $index->getProcessorSettings(); - $processors['html_filter'] = array( - 'processor_id' => 'html_filter', - 'weights' => array(), - 'settings' => array(), - ); - $index->setProcessorSettings($processors); + $processor = \Drupal::getContainer() + ->get('plugin.manager.search_api.processor') + ->createInstance('html_filter'); + + $index->addProcessor($processor); $index->save(); + + $this->assertArrayHasKey('html_filter', $index->getProcessors(), 'HTML filter processor is added.'); + $this->assertArrayHasKey('body', $index->getFields(), 'Body field is added.'); } /** @@ -216,11 +220,12 @@ class BackendTest extends KernelTestBase { protected function disableHtmlFilter() { /** @var \Drupal\search_api\IndexInterface $index */ $index = $this->getIndex(); - $processors = $index->getProcessorSettings(); - unset($processors['html_filter']); - $index->setProcessorSettings($processors); $index->removeField('body'); + $index->removeProcessor('html_filter'); $index->save(); + + $this->assertArrayNotHasKey('html_filter', $index->getProcessors(), 'HTML filter processor is removed.'); + $this->assertArrayNotHasKey('body', $index->getFields(), 'Body field is removed.'); } /** @@ -873,7 +878,6 @@ class BackendTest extends KernelTestBase { // Regression test for #1916474. /** @var \Drupal\search_api\IndexInterface $index */ $index = $this->getIndex(); - $index->resetCaches(); $this->addField($index, 'prices', 'decimal'); $success = $index->save(); $this->assertTrue($success, 'The index field settings were successfully changed.'); @@ -1072,6 +1076,7 @@ class BackendTest extends KernelTestBase { ); $field = Utility::createField($index, $property_name, $field_info); $index->addField($field); + $index->save(); } } diff --git a/src/Entity/Index.php b/src/Entity/Index.php index a993e44..95078e6 100644 --- a/src/Entity/Index.php +++ b/src/Entity/Index.php @@ -18,6 +18,7 @@ use Drupal\search_api\Query\QueryInterface; use Drupal\search_api\Query\ResultSetInterface; use Drupal\search_api\SearchApiException; use Drupal\search_api\ServerInterface; +use Drupal\search_api\Tracker\TrackerInterface; use Drupal\search_api\Utility; use Drupal\user\TempStoreException; use Drupal\views\Views; @@ -57,13 +58,11 @@ use Drupal\views\Views; * "name", * "description", * "read_only", - * "fields", - * "processors", + * "field_settings", + * "processor_settings", * "options", - * "datasources", - * "datasource_configs", - * "tracker", - * "tracker_config", + * "datasource_settings", + * "tracker_settings", * "server", * }, * links = { @@ -115,7 +114,18 @@ class Index extends ConfigEntityBase implements IndexInterface { * * @var array */ - protected $fields = array(); + protected $field_settings = array(); + + /** + * An array of field instances. + * + * In the ::preSave method we're saving the contents of these back into the + * $field_settings array. When adding, removing or changing configuration we + * should always use these. + * + * @var \Drupal\search_api\Item\FieldInterface[]|null + */ + protected $fieldInstances = NULL; /** * An array of options configuring this index. @@ -127,50 +137,60 @@ class Index extends ConfigEntityBase implements IndexInterface { protected $options = array(); /** - * The IDs of the datasources selected for this index. + * The settings of the datasources selected for this index. * - * @var string[] - */ - protected $datasources = array(); - - /** - * The configuration for the selected datasources. + * The array has the following structure: + * array( + * DS => array( + * 'plugin_id' => 'DS', + * 'settings' => array(), + * ), + * ) * - * @var array + * @var string[] */ - protected $datasource_configs = array(); + protected $datasource_settings = array(); /** * The instantiated datasource plugins. * + * In the ::preSave method we're saving the contents of these back into the + * $datasource_settings array. When adding, removing or changing configuration + * we should always use these. + * * @var \Drupal\search_api\Datasource\DatasourceInterface[]|null * * @see getDatasources() */ - protected $datasourcePlugins; + protected $datasourceInstances = NULL; /** - * The tracker plugin ID. + * The tracker settings * - * @var string - */ - protected $tracker = 'default'; - - /** - * The tracker plugin configuration. + * The array has the following structure: + * array( + * TS => array( + * 'plugin_id' => 'TS', + * 'settings' => array(), + * ), + * ) * - * @var array + * @var string[] */ - protected $tracker_config = array(); + protected $tracker_settings = NULL; /** * The tracker plugin instance. * + * In the ::preSave method we're saving the contents of these back into the + * $tracker_settings array. When adding, removing or changing configuration + * we should always use this instance. + * * @var \Drupal\search_api\Tracker\TrackerInterface|null * - * @see getTracker() + * @see getTrackerInstance() */ - protected $trackerPlugin; + protected $trackerInstance; /** * The ID of the server on which data should be indexed. @@ -184,7 +204,7 @@ class Index extends ConfigEntityBase implements IndexInterface { * * @var \Drupal\search_api\ServerInterface * - * @see getServer() + * @see getServerInstance() */ protected $serverInstance; @@ -198,19 +218,30 @@ class Index extends ConfigEntityBase implements IndexInterface { /** * The array of processor settings. * + * The array has the following structure: + * array( + * PS => array( + * 'plugin_id' => 'PS', + * 'settings' => array('weights' => array(), ...), + * ), + * ) + * * @var array * An array containing processor settings. */ - protected $processors = array(); + protected $processor_settings = array(); /** - * Cached information about the processors available for this index. + * Instances of the processor plugins. * + * In the ::preSave method we're saving the contents of these back into the + * $tracker_settings array. When adding, removing or changing configuration + * we should always use this instance. * @var \Drupal\search_api\Processor\ProcessorInterface[]|null * * @see loadProcessors() */ - protected $processorPlugins; + protected $processorInstances = NULL; /** * Whether reindexing has been triggered for this index in this page request. @@ -300,8 +331,15 @@ class Index extends ConfigEntityBase implements IndexInterface { /** * {@inheritdoc} */ + public function setDatasources(array $datasources = NULL) { + $this->datasourceInstances = $datasources; + } + + /** + * {@inheritdoc} + */ public function getDatasourceIds() { - return $this->datasources; + return array_keys($this->getDatasources()); } /** @@ -317,11 +355,13 @@ class Index extends ConfigEntityBase implements IndexInterface { */ public function getDatasource($datasource_id) { $datasources = $this->getDatasources(); + if (empty($datasources[$datasource_id])) { $args['@datasource'] = $datasource_id; $args['%index'] = $this->label(); throw new SearchApiException(new FormattableMarkup('The datasource with ID "@datasource" could not be retrieved for index %index.', $args)); } + return $datasources[$datasource_id]; } @@ -329,62 +369,88 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function getDatasources($only_enabled = TRUE) { - if (!isset($this->datasourcePlugins)) { - $this->datasourcePlugins = array(); - /** @var $datasource_plugin_manager \Drupal\search_api\Datasource\DatasourcePluginManager */ - $datasource_plugin_manager = \Drupal::service('plugin.manager.search_api.datasource'); - - foreach ($datasource_plugin_manager->getDefinitions() as $name => $datasource_definition) { - if (class_exists($datasource_definition['class']) && empty($this->datasourcePlugins[$name])) { - // Create our settings for this datasource. - $config = isset($this->datasource_configs[$name]) ? $this->datasource_configs[$name] : array(); - $config += array('index' => $this); - - /** @var $datasource \Drupal\search_api\Datasource\DatasourceInterface */ - $datasource = $datasource_plugin_manager->createInstance($name, $config); - $this->datasourcePlugins[$name] = $datasource; - } - elseif (!class_exists($datasource_definition['class'])) { - \Drupal::logger('search_api')->warning('Datasource @id specifies a non-existing @class.', array('@id' => $name, '@class' => $datasource_definition['class'])); - } + if ($only_enabled && !is_null($this->datasourceInstances)) { + return $this->datasourceInstances; + } + + $all_datasources = array(); + /** @var $datasource_plugin_manager \Drupal\search_api\Datasource\DatasourcePluginManager */ + $datasource_plugin_manager = \Drupal::service('plugin.manager.search_api.datasource'); + + foreach ($datasource_plugin_manager->getDefinitions() as $name => $datasource_definition) { + if (class_exists($datasource_definition['class']) && empty($all_datasources[$name])) { + // Create our settings for this datasource. + $config = isset($this->datasource_settings[$name]) ? $this->datasource_settings[$name]['settings'] : array(); + $config += array('index' => $this); + + /** @var $datasource \Drupal\search_api\Datasource\DatasourceInterface */ + $datasource = $datasource_plugin_manager->createInstance($name, $config); + $all_datasources[$name] = $datasource; + } + elseif (!class_exists($datasource_definition['class'])) { + \Drupal::logger('search_api')->warning('Datasource @id specifies a non-existing @class.', array('@id' => $name, '@class' => $datasource_definition['class'])); } } // Filter datasources by status if required. if (!$only_enabled) { - return $this->datasourcePlugins; + return $all_datasources; } - return array_intersect_key($this->datasourcePlugins, array_flip($this->datasources)); + + $enabled_datasources = array_intersect_key($all_datasources, $this->datasource_settings); + $this->datasourceInstances = $enabled_datasources; + + return $enabled_datasources; } /** * {@inheritdoc} */ public function hasValidTracker() { - return (bool) \Drupal::service('plugin.manager.search_api.tracker')->getDefinition($this->getTrackerId(), FALSE); + return (bool) \Drupal::service('plugin.manager.search_api.tracker') + ->getDefinition($this->getTrackerId(), FALSE); } /** * {@inheritdoc} */ public function getTrackerId() { - return $this->tracker; + return $this->getTrackerInstance()->getPluginId(); } /** * {@inheritdoc} */ - public function getTracker() { - if (!$this->trackerPlugin) { - $tracker_plugin_configuration = array('index' => $this) + $this->tracker_config; - if (!($this->trackerPlugin = \Drupal::service('plugin.manager.search_api.tracker')->createInstance($this->getTrackerId(), $tracker_plugin_configuration))) { - $args['@tracker'] = $this->tracker; + public function getTrackerInstance() { + if (!$this->trackerInstance) { + + if (is_null($this->tracker_settings)) { + $tracker_plugin_configuration = array('index' => $this); + $plugin_id = 'default'; + } + else { + $plugins = array_keys($this->tracker_settings); + $tracker_plugin_configuration = array('index' => $this) + $this->tracker_settings[$plugins[0]]['settings']; + $plugin_id = $this->tracker_settings[$plugins[0]]['plugin_id']; + } + + if (!($this->trackerInstance = \Drupal::service('plugin.manager.search_api.tracker')->createInstance($plugin_id, $tracker_plugin_configuration))) { + $args['@tracker'] = $this->getTrackerId(); $args['%index'] = $this->label(); throw new SearchApiException(new FormattableMarkup('The tracker with ID "@tracker" could not be retrieved for index %index.', $args)); } } - return $this->trackerPlugin; + return $this->trackerInstance; + } + + /** + * {@inheritdoc} + */ + public function setTracker(TrackerInterface $tracker) { + $this->trackerInstance = $tracker; + + return $this; } /** @@ -398,7 +464,7 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function isServerEnabled() { - return $this->hasValidServer() && $this->getServer()->status(); + return $this->hasValidServer() && $this->getServerInstance()->status(); } /** @@ -411,7 +477,7 @@ class Index extends ConfigEntityBase implements IndexInterface { /** * {@inheritdoc} */ - public function getServer() { + public function getServerInstance() { if (!$this->serverInstance && $this->server) { $this->serverInstance = Server::load($this->server); if (!$this->serverInstance) { @@ -436,16 +502,30 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function getProcessors($only_enabled = TRUE) { - $processors = $this->loadProcessors(); + if (!$only_enabled) { + return $this->loadProcessors(); + } - // Filter processors by status if required. Enabled processors are those - // which have settings in the "processors" option. - if ($only_enabled) { - $processors_settings = $this->getProcessorSettings(); - $processors = array_intersect_key($processors, $processors_settings); + if (!is_null($this->processorInstances)) { + return $this->processorInstances; } - return $processors; + $processors = $this->loadProcessors(); + + // Filter the processors by status, ::loadProcessors() returns an array of + // enabled processors for all possible processors. + // Because we usually return early from this method if ::$processorInstances + // is already set we can safely assume that ::$processor_settings still + // holds the enabled processors we're settings ::$processorInstances to an + // array of processors that only have the same ID's as the ones in + // ::$processor_settings. + // + // We should only reach this point in the code once after the index is + // loaded. + $processors_settings = $this->processor_settings; + $this->processorInstances = array_intersect_key($processors, $processors_settings); + + return $this->processorInstances; } /** @@ -453,15 +533,15 @@ class Index extends ConfigEntityBase implements IndexInterface { */ public function getProcessorsByStage($stage, $only_enabled = TRUE) { $processors = $this->loadProcessors(); - $processor_settings = $this->getProcessorSettings(); + $processor_settings = $this->processor_settings; $processor_weights = array(); // Get a list of all processors meeting the criteria (stage and, optionally, // enabled) along with their effective weights (user-set or default). foreach ($processors as $name => $processor) { if ($processor->supportsStage($stage) && !($only_enabled && empty($processor_settings[$name]))) { - if (!empty($processor_settings[$name]['weights'][$stage])) { - $processor_weights[$name] = $processor_settings[$name]['weights'][$stage]; + if (!empty($processor_settings[$name]['settings']['weights'][$stage])) { + $processor_weights[$name] = $processor_settings[$name]['settings']['weights'][$stage]; } else { $processor_weights[$name] = $processor->getDefaultWeight($stage); @@ -476,55 +556,64 @@ class Index extends ConfigEntityBase implements IndexInterface { foreach ($processor_weights as $name => $weight) { $return_processors[$name] = $processors[$name]; } + return $return_processors; } /** - * Retrieves all processors supported by this index. - * - * @return \Drupal\search_api\Processor\ProcessorInterface[] - * The loaded processors, keyed by processor ID. + * {@inheritdoc} */ - protected function loadProcessors() { - if (empty($this->processorPlugins)) { - /** @var $processor_plugin_manager \Drupal\search_api\Processor\ProcessorPluginManager */ - $processor_plugin_manager = \Drupal::service('plugin.manager.search_api.processor'); - $processor_settings = $this->getProcessorSettings(); - - foreach ($processor_plugin_manager->getDefinitions() as $name => $processor_definition) { - if (class_exists($processor_definition['class']) && empty($this->processorPlugins[$name])) { - // Create our settings for this processor. - $settings = empty($processor_settings[$name]['settings']) ? array() : $processor_settings[$name]['settings']; - $settings['index'] = $this; - - /** @var $processor \Drupal\search_api\Processor\ProcessorInterface */ - $processor = $processor_plugin_manager->createInstance($name, $settings); - if ($processor->supportsIndex($this)) { - $this->processorPlugins[$name] = $processor; - } - } - elseif (!class_exists($processor_definition['class'])) { - \Drupal::logger('search_api')->warning('Processor @id specifies a non-existing @class.', array('@id' => $name, '@class' => $processor_definition['class'])); - } - } - } + public function addProcessor(ProcessorInterface $processor) { + $this->processorInstances[$processor->getPluginId()] = $processor; - return $this->processorPlugins; + return $this; } /** * {@inheritdoc} */ - public function getProcessorSettings() { - return $this->processors; + public function removeProcessor($processor_id) { + // Make sure the processorInstances are loaded before trying to remove a + // plugin from them. + if (is_null($this->processorInstances)) { + $this->getProcessors(); + } + unset($this->processorInstances[$processor_id]); + + return $this; } /** - * {@inheritdoc} + * Retrieves all processors supported by this index. + * + * @return \Drupal\search_api\Processor\ProcessorInterface[] + * The loaded processors, keyed by processor ID. */ - public function setProcessorSettings(array $processors) { - $this->processors = $processors; - return $this; + protected function loadProcessors() { + $processor_instances = array(); + + /** @var $processor_plugin_manager \Drupal\search_api\Processor\ProcessorPluginManager */ + $processor_plugin_manager = \Drupal::service('plugin.manager.search_api.processor'); + $processor_settings = $this->processor_settings; + + foreach ($processor_plugin_manager->getDefinitions() as $name => $processor_definition) { + if (class_exists($processor_definition['class']) && empty($processor_instances[$name])) { + // Create our settings for this processor. + $settings = empty($processor_settings[$name]['settings']) ? array() : $processor_settings[$name]['settings']; + $settings['index'] = $this; + + /** @var $processor \Drupal\search_api\Processor\ProcessorInterface */ + $processor = $processor_plugin_manager->createInstance($name, $settings); + if ($processor->supportsIndex($this)) { + $processor_instances[$name] = $processor; + } + } + elseif (!class_exists($processor_definition['class'])) { + \Drupal::logger('search_api')->warning('Processor @id specifies a non-existing @class.', array('@id' => $name, '@class' => $processor_definition['class'])); + } + } + + return $processor_instances; } /** @@ -578,9 +667,8 @@ class Index extends ConfigEntityBase implements IndexInterface { throw new SearchApiException(new FormattableMarkup('Cannot add field with machine name %field_id: machine name is already taken.', $args)); } - $this->fields[$field_id] = $field->getSettings(); + $this->fieldInstances[$field_id] = $field; - $this->resetCaches(); return $this; } @@ -588,7 +676,7 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function renameField($old_field_id, $new_field_id) { - if (isset($this->fields[$old_field_id])) { + if (isset($this->getFields()[$old_field_id])) { $args['%field_id'] = $old_field_id; throw new SearchApiException(new FormattableMarkup('Could not rename field with machine name %field_id: no such field.', $args)); } @@ -596,15 +684,14 @@ class Index extends ConfigEntityBase implements IndexInterface { $args['%field_id'] = $new_field_id; throw new SearchApiException(new FormattableMarkup('%field_id is a reserved value and cannot be used as the machine name of a normal field.', $args)); } - if (isset($this->fields[$new_field_id])) { + if (isset($this->getFields()[$new_field_id])) { $args['%field_id'] = $new_field_id; - throw new SearchApiException(new FormattableMarkup('%field_id is a reserved value and cannot be used as the machine name of a normal field.', $args)); + throw new SearchApiException(new FormattableMarkup("%field_id already exists and can't be used as a new field id.", $args)); } - $this->fields[$new_field_id] = $this->fields[$old_field_id]; - unset($this->fields[$old_field_id]); + $this->fieldInstances[$new_field_id] = $this->fieldInstances[$old_field_id]; + unset($this->fieldInstances[$old_field_id]); - $this->resetCaches(); return $this; } @@ -621,9 +708,8 @@ class Index extends ConfigEntityBase implements IndexInterface { throw new SearchApiException(new FormattableMarkup('Cannot remove field with machine name %field_id: field is locked.', $args)); } - unset($this->fields[$field_id]); + unset($this->fieldInstances[$field_id]); - $this->resetCaches(); return $this; } @@ -631,14 +717,17 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function getFields() { - $fields = $this->getCache(__FUNCTION__, FALSE); - if (!$fields) { - $fields = array(); - foreach ($this->getFieldSettings() as $key => $field_info) { - $fields[$key] = Utility::createField($this, $key, $field_info); - } + // ::$fieldInstances is already filled with fields, so keep on using those. + if (!is_null($this->fieldInstances)) { + return $this->fieldInstances; + } + + $fields = array(); + foreach ($this->field_settings as $key => $field_info) { + $fields[$key] = Utility::createField($this, $key, $field_info); } - $this->setCache(__FUNCTION__, $fields, FALSE); + + $this->fieldInstances = $fields; return $fields; } @@ -655,14 +744,10 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function getFieldsByDatasource($datasource_id) { - $datasource_fields = $this->getCache(__FUNCTION__); - if (!$datasource_fields) { - $datasource_fields = array_fill_keys($this->datasources, array()); - $datasource_fields[NULL] = array(); - foreach ($this->getFields() as $field_id => $field) { - $datasource_fields[$field->getDatasourceId()][$field_id] = $field; - } - $this->setCache(__FUNCTION__, $datasource_fields); + $datasource_fields = array_fill_keys(array_keys($this->getDatasources()), array()); + $datasource_fields[NULL] = array(); + foreach ($this->getFields() as $field_id => $field) { + $datasource_fields[$field->getDatasourceId()][$field_id] = $field; } return $datasource_fields[$datasource_id]; @@ -672,15 +757,11 @@ class Index extends ConfigEntityBase implements IndexInterface { * {@inheritdoc} */ public function getFulltextFields() { - $fulltext_fields = $this->getCache(__FUNCTION__); - if (!$fulltext_fields) { - $fulltext_fields = array(); - foreach ($this->getFieldSettings() as $key => $field_info) { - if (Utility::isTextType($field_info['type'])) { - $fulltext_fields[] = $key; - } + $fulltext_fields = array(); + foreach ($this->getFields() as $key => $field) { + if (Utility::isTextType($field->getType())) { + $fulltext_fields[] = $key; } - $this->setCache(__FUNCTION__, $fulltext_fields); } return $fulltext_fields; } @@ -688,39 +769,20 @@ class Index extends ConfigEntityBase implements IndexInterface { /** * {@inheritdoc} */ - public function getFieldSettings() { - return $this->fields; - } - - /** - * {@inheritdoc} - */ - public function setFieldSettings(array $fields = array()) { - $this->fields = $fields; - return $this; - } - - /** - * {@inheritdoc} - */ public function getPropertyDefinitions($datasource_id, $alter = TRUE) { $alter = $alter ? 1 : 0; - $properties = $this->getCache(__FUNCTION__); - if (!isset($properties[$datasource_id][$alter])) { - if (isset($datasource_id)) { - $datasource = $this->getDatasource($datasource_id); - $properties[$datasource_id][$alter] = $datasource->getPropertyDefinitions(); - } - else { - $datasource = NULL; - $properties[$datasource_id][$alter] = array(); - } - if ($alter) { - foreach ($this->getProcessors() as $processor) { - $processor->alterPropertyDefinitions($properties[$datasource_id][$alter], $datasource); - } + if (isset($datasource_id)) { + $datasource = $this->getDatasource($datasource_id); + $properties[$datasource_id][$alter] = $datasource->getPropertyDefinitions(); + } + else { + $datasource = NULL; + $properties[$datasource_id][$alter] = array(); + } + if ($alter) { + foreach ($this->getProcessors() as $processor) { + $processor->alterPropertyDefinitions($properties[$datasource_id][$alter], $datasource); } - $this->setCache(__FUNCTION__, $properties); } return $properties[$datasource_id][$alter]; } @@ -767,7 +829,7 @@ class Index extends ConfigEntityBase implements IndexInterface { */ public function indexItems($limit = '-1', $datasource_id = NULL) { if ($this->hasValidTracker() && !$this->isReadOnly()) { - $tracker = $this->getTracker(); + $tracker = $this->getTrackerInstance(); $next_set = $tracker->getRemainingItems($limit, $datasource_id); $items = $this->loadItemsMultiple($next_set); if (count($items) != count($next_set)) { @@ -799,7 +861,7 @@ class Index extends ConfigEntityBase implements IndexInterface { if (!$this->status) { throw new SearchApiException(new FormattableMarkup("Couldn't index values on index %index (index is disabled)", array('%index' => $this->label()))); } - if (empty($this->fields)) { + if (empty($this->getFields())) { throw new SearchApiException(new FormattableMarkup("Couldn't index values on index %index (no fields selected)", array('%index' => $this->label()))); } @@ -830,12 +892,12 @@ class Index extends ConfigEntityBase implements IndexInterface { // Items that are rejected should also be deleted from the server. if ($rejected_ids) { - $this->getServer()->deleteItems($this, $rejected_ids); + $this->getServerInstance()->deleteItems($this, $rejected_ids); } $indexed_ids = array(); if ($items) { - $indexed_ids = $this->getServer()->indexItems($this, $items); + $indexed_ids = $this->getServerInstance()->indexItems($this, $items); } // Return the IDs of all items that were either successfully indexed or @@ -844,7 +906,7 @@ class Index extends ConfigEntityBase implements IndexInterface { if ($processed_ids) { if ($this->hasValidTracker()) { - $this->getTracker()->trackItemsIndexed($processed_ids); + $this->getTrackerInstance()->trackItemsIndexed($processed_ids); } // Since we've indexed items now, triggering reindexing would have some // effect again. Therefore, we reset the flag. @@ -889,7 +951,7 @@ class Index extends ConfigEntityBase implements IndexInterface { foreach ($ids as $id) { $item_ids[] = Utility::createCombinedId($datasource_id, $id); } - $this->getTracker()->$tracker_method($item_ids); + $this->getTrackerInstance()->$tracker_method($item_ids); if (!$this->isReadOnly() && $this->getOption('index_directly')) { try { $items = $this->loadItemsMultiple($item_ids); @@ -913,9 +975,9 @@ class Index extends ConfigEntityBase implements IndexInterface { foreach ($ids as $id) { $item_ids[] = Utility::createCombinedId($datasource_id, $id); } - $this->getTracker()->trackItemsDeleted($item_ids); + $this->getTrackerInstance()->trackItemsDeleted($item_ids); if (!$this->isReadOnly() && $this->isServerEnabled()) { - $this->getServer()->deleteItems($this, $item_ids); + $this->getServerInstance()->deleteItems($this, $item_ids); } } } @@ -926,7 +988,7 @@ class Index extends ConfigEntityBase implements IndexInterface { public function reindex() { if ($this->status() && !$this->hasReindexed) { $this->hasReindexed = TRUE; - $this->getTracker()->trackAllItemsUpdated(); + $this->getTrackerInstance()->trackAllItemsUpdated(); \Drupal::moduleHandler()->invokeAll('search_api_index_reindex', array($this, FALSE)); } } @@ -941,11 +1003,11 @@ class Index extends ConfigEntityBase implements IndexInterface { if (!$this->hasReindexed) { $invoke_hook = TRUE; $this->hasReindexed = TRUE; - $this->getTracker()->trackAllItemsUpdated(); + $this->getTrackerInstance()->trackAllItemsUpdated(); } if (!$this->isReadOnly()) { $invoke_hook = TRUE; - $this->getServer()->deleteAllIndexItems($this); + $this->getServerInstance()->deleteAllIndexItems($this); } if ($invoke_hook) { \Drupal::moduleHandler()->invokeAll('search_api_index_reindex', array($this, !$this->isReadOnly())); @@ -961,76 +1023,6 @@ class Index extends ConfigEntityBase implements IndexInterface { } /** - * Retrieves data from the static and/or stored cache. - * - * @param string $method - * The name of the method for which data is requested. Used to construct the - * cache ID. - * @param bool $static_only - * (optional) If TRUE, only consult the static cache for this page request, - * don't attempt to load the value from the stored cache. - * - * @return mixed|null - * The cached data, or NULL if it could not be found. - */ - protected function getCache($method, $static_only = TRUE) { - if (isset($this->cache[$method])) { - return $this->cache[$method]; - } - - if (!$static_only) { - $cid = $this->getCacheId($method); - if ($cached = \Drupal::cache()->get($cid)) { - if (is_array($cached->data)) { - $this->updateFieldsIndex($cached->data); - } - $this->cache[$method] = $cached->data; - return $this->cache[$method]; - } - } - - return NULL; - } - - /** - * Sets data in the static and/or stored cache. - * - * @param string $method - * The name of the method for which cached data should be set. Used to - * construct the cache ID. - * @param mixed $value - * The value to set for the cache. - * @param bool $static_only - * (optional) If TRUE, only set the value in the static cache for this page - * request, not in the stored cache. - * - * @return $this - */ - protected function setCache($method, $value, $static_only = TRUE) { - $this->cache[$method] = $value; - if (!$static_only) { - $cid = $this->getCacheId($method); - \Drupal::cache() - ->set($cid, $value, Cache::PERMANENT, $this->getCacheTags()); - } - return $this; - } - - /** - * {@inheritdoc} - */ - public function resetCaches($include_stored = TRUE) { - $this->datasourcePlugins = NULL; - $this->trackerPlugin = NULL; - $this->serverInstance = NULL; - $this->processorPlugins = NULL; - $this->cache = array(); - if ($include_stored) { - Cache::invalidateTags($this->getCacheTags()); - } - } - - /** * Sets this object as the index for all fields contained in the given array. * * This is important when loading fields from the cache, because their index @@ -1073,33 +1065,57 @@ class Index extends ConfigEntityBase implements IndexInterface { $this->disable(); } - // Remove all "locked" and "hidden" flags from all fields of the index. If - // they are still valid, they should be re-added by the processors. - foreach ($this->fields as $field_id => $field_settings) { - unset($this->fields[$field_id]['indexed_locked']); - unset($this->fields[$field_id]['type_locked']); - unset($this->fields[$field_id]['hidden']); - } - // We first have to check for locked processors, otherwise their // preIndexSave() methods might not be called in the next step. + $this->processor_settings = array(); foreach ($this->getProcessors(FALSE) as $processor_id => $processor) { - if ($processor->isLocked() && !isset($this->processors[$processor_id])) { - $this->processors[$processor_id] = array( - 'processor_id' => $processor_id, - 'weights' => array(), + if ($processor->isLocked() && !isset($this->processor_settings[$processor_id])) { + $this->processor_settings[$processor_id] = array( + 'plugin_id' => $processor_id, 'settings' => array(), ); } } - // Reset the cache so the used processors and fields will be up-to-date. - $this->resetCaches(); + foreach ($this->getProcessors() as $processor_id => $processor) { + $this->processor_settings[$processor_id] = array( + 'plugin_id' => $processor_id, + 'settings' => $processor->getConfiguration(), + ); + } + + // Reset the canonical representation of the processors by running + // ::getProcessors() again here after resetting them back to NULL. This is + // only needed when creating a new Index. + $this->processorInstances = NULL; + $this->getProcessors(); + + // Add tracker settings. + $this->tracker_settings = array( + $this->getTrackerId() => array( + 'plugin_id' => $this->getTrackerId(), + 'settings' => $this->getTrackerInstance()->getConfiguration(), + ) + ); + + // Dump the active datasources to the settings array. + $this->datasource_settings = array(); + foreach ($this->getDatasources() as $plugin_id => $datasource) { + $this->datasource_settings[$plugin_id] = array( + 'plugin_id' => $plugin_id, + 'settings' => $datasource->getConfiguration(), + ); + } // Call the preIndexSave() method of all applicable processors. foreach ($this->getProcessorsByStage(ProcessorInterface::STAGE_PRE_INDEX_SAVE) as $processor) { $processor->preIndexSave(); } + + $this->field_settings = array(); + foreach ($this->getFields() as $field_id => $field) { + $this->field_settings[$field_id] = $field->getSettings(); + } } /** @@ -1107,10 +1123,10 @@ class Index extends ConfigEntityBase implements IndexInterface { */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - $this->resetCaches(); try { // Fake an original for inserts to make code cleaner. + /** @var \Drupal\search_api\IndexInterface $original */ $original = $update ? $this->original : static::create(array('status' => FALSE)); $index_task_manager = \Drupal::getContainer() ->get('search_api.index_task_manager'); @@ -1127,11 +1143,11 @@ class Index extends ConfigEntityBase implements IndexInterface { $index_task_manager->stopTracking($this); } if ($original->isServerEnabled()) { - $original->getServer()->removeIndex($this); + $original->getServerInstance()->removeIndex($this); } } elseif ($this->status() && !$original->status()) { - $this->getServer()->addIndex($this); + $this->getServerInstance()->addIndex($this); if ($this->hasValidTracker()) { $index_task_manager->startTracking($this); } @@ -1157,7 +1173,7 @@ class Index extends ConfigEntityBase implements IndexInterface { \Drupal::cache('discovery')->delete('views:wizard'); } - $this->resetCaches(); + Cache::invalidateTags($this->getCacheTags()); } catch (SearchApiException $e) { watchdog_exception('search_api', $e); @@ -1180,17 +1196,17 @@ class Index extends ConfigEntityBase implements IndexInterface { if ($this->getServerId() != $original->getServerId()) { if ($original->isServerEnabled()) { - $original->getServer()->removeIndex($this); + $original->getServerInstance()->removeIndex($this); } if ($this->isServerEnabled()) { - $this->getServer()->addIndex($this); + $this->getServerInstance()->addIndex($this); } // When the server changes we also need to trigger a reindex. $this->reindex(); } elseif ($this->isServerEnabled()) { // Tell the server the index configuration got updated - $this->getServer()->updateIndex($this); + $this->getServerInstance()->updateIndex($this); } } @@ -1234,7 +1250,7 @@ class Index extends ConfigEntityBase implements IndexInterface { // enabled afterwards. Otherwise, this method should not be called. assert('$this->status() && $original->status()', '::reactToTrackerSwitch should only be called when the index is enabled'); - if ($this->tracker != $original->getTrackerId()) { + if ($this->getTrackerId() != $original->getTrackerId()) { $index_task_manager = \Drupal::getContainer()->get('search_api.index_task_manager'); if ($original->hasValidTracker()) { $index_task_manager->stopTracking($this); @@ -1252,21 +1268,20 @@ class Index extends ConfigEntityBase implements IndexInterface { * The previous version of the index. */ protected function reactToProcessorChanges(IndexInterface $original) { - $original_settings = $original->getProcessorSettings(); - $new_settings = $this->getProcessorSettings(); + $old_processors = $original->getProcessors(); + $new_processors = $this->getProcessors(); // Only actually do something when the processor settings are changed. - if ($original_settings != $new_settings) { + if ($old_processors != $new_processors) { $requires_reindex = FALSE; - $processors = $this->getProcessors(FALSE); // Loop over all new settings and check if the processors were already set // in the original entity. - foreach ($new_settings as $key => $setting) { + foreach ($new_processors as $key => $processor) { // The processor is new, because it wasn't configured in the original // entity. - if (!isset($original_settings[$key])) { - if ($processors[$key]->requiresReindexing(NULL, $new_settings[$key])) { + if (!isset($old_processors[$key])) { + if ($processor->requiresReindexing(NULL, $processor->getConfiguration())) { $requires_reindex = TRUE; break; } @@ -1276,17 +1291,12 @@ class Index extends ConfigEntityBase implements IndexInterface { if (!$requires_reindex) { // Loop over all original settings and check if one of them has been // removed or changed. - foreach ($original_settings as $key => $old_processor_settings) { - // If the processor isn't even available any more, we can't determine - // what it would have said about the need to reindex. Err on the side - // of caution and guess "yes". - if (empty($processors[$key])) { - $requires_reindex = TRUE; - break; - } - $new_processor_settings = isset($new_settings[$key]) ? $new_settings[$key] : NULL; - if (!isset($new_processor_settings) || $new_processor_settings != $old_processor_settings) { - if ($processors[$key]->requiresReindexing($old_processor_settings, $new_processor_settings)) { + foreach ($old_processors as $key => $old_processor) { + $new_processor = isset($new_processors[$key]) ? $new_processors[$key] : NULL; + $old_config = $old_processor->getConfiguration(); + $new_config = $new_processor ? $new_processor->getConfiguration() : NULL; + if (!$new_processor || $old_config != $new_config) { + if ($old_processor->requiresReindexing($old_config, $new_config)) { $requires_reindex = TRUE; break; } @@ -1310,10 +1320,10 @@ class Index extends ConfigEntityBase implements IndexInterface { /** @var \Drupal\search_api\IndexInterface[] $entities */ foreach ($entities as $index) { if ($index->hasValidTracker()) { - $index->getTracker()->trackAllItemsDeleted(); + $index->getTrackerInstance()->trackAllItemsDeleted(); } if ($index->hasValidServer()) { - $index->getServer()->removeIndex($index); + $index->getServerInstance()->removeIndex($index); } } } @@ -1420,7 +1430,7 @@ class Index extends ConfigEntityBase implements IndexInterface { // The server needs special treatment, since it is a dependency of the index // itself, and not one of its plugins. if ($this->hasValidServer()) { - $name = $this->getServer()->getConfigDependencyName(); + $name = $this->getServerInstance()->getConfigDependencyName(); $dependency_data['config'][$name]['optional']['index'][$this->id] = $this; } @@ -1540,18 +1550,34 @@ class Index extends ConfigEntityBase implements IndexInterface { // The handling of how we translate plugin changes back to the index varies // according to plugin type, unfortunately. // First, remove plugins that need to be removed. - $this->processors = array_intersect_key($this->processors, $all_plugins['processors']); - $this->datasources = array_keys($all_plugins['datasources']); - $this->datasource_configs = array_intersect_key($this->datasource_configs, $all_plugins['datasources']); - // There always needs to be a tracker. + $this->processor_settings = array_intersect_key($this->processor_settings, $all_plugins['processors']); + $this->processorInstances = array_intersect_key($this->processorInstances, $all_plugins['processors']); + + $this->datasource_settings = array_intersect_key($this->datasource_settings, $all_plugins['datasources']); + $this->datasourceInstances = array_intersect_key($this->datasourceInstances, $all_plugins['datasources']); + + // There always needs to be a tracker so reset it back to the default + // tracker. if (empty($all_plugins['tracker'])) { - $this->tracker = \Drupal::config('search_api.settings')->get('default_tracker'); - $this->tracker_config = array(); + $default_tracker_id = \Drupal::config('search_api.settings') + ->get('default_tracker'); + + $this->tracker_settings = array( + $default_tracker_id = array( + 'plugin_id' => $default_tracker_id, + 'settings' => array(), + ) + ); + + // Repopulate the tracker instance with the new settings. + $this->trackerInstance = NULL; + $this->getTrackerInstance(); } + // There also always needs to be a datasource, but here we have no easy way // out – if we had to remove all datasources, the operation fails. Return // FALSE to indicate this, which will cause the index to be deleted. - if (!$this->datasources) { + if (!$this->datasource_settings) { return FALSE; } @@ -1560,22 +1586,18 @@ class Index extends ConfigEntityBase implements IndexInterface { foreach ($plugin_configs as $plugin_id => $plugin_config) { switch ($plugin_type) { case 'processors': - $this->processors[$plugin_id]['settings'] = $plugin_config; + $this->processor_settings[$plugin_id]['settings'] = $plugin_config; break; case 'datasources': - $this->datasource_configs[$plugin_id] = $plugin_config; + $this->datasource_settings[$plugin_id]['settings'] = $plugin_config; break; case 'tracker': - $this->tracker_config = $plugin_config; + $this->tracker_settings[$plugin_id]['settings'] = $plugin_config; break; } } } - if ($changed) { - $this->resetCaches(); - } - return $changed; } @@ -1590,7 +1612,7 @@ class Index extends ConfigEntityBase implements IndexInterface { $plugins = array(); if ($this->hasValidTracker()) { - $plugins['tracker'][$this->getTrackerId()] = $this->getTracker(); + $plugins['tracker'][$this->getTrackerId()] = $this->getTrackerInstance(); } $plugins['processors'] = $this->getProcessors(); $plugins['datasources'] = $this->getDatasources(); @@ -1599,26 +1621,17 @@ class Index extends ConfigEntityBase implements IndexInterface { } /** - * Implements the magic __clone() method. - * - * Prevents the cached plugins and fields from being cloned, too (since they - * would then point to the wrong index object). - */ - public function __clone() { - $this->resetCaches(FALSE); - } - - /** * Implements the magic __sleep() method. * * Prevents the cached plugins and fields from being serialized. */ public function __sleep() { $properties = get_object_vars($this); - unset($properties['datasourcePlugins']); - unset($properties['trackerPlugin']); + unset($properties['datasourceInstances']); + unset($properties['trackerInstance']); unset($properties['serverInstance']); - unset($properties['processorPlugins']); + unset($properties['processorInstances']); + unset($properties['fieldInstances']); unset($properties['cache']); return array_keys($properties); } diff --git a/src/Form/IndexFieldsForm.php b/src/Form/IndexFieldsForm.php index 4b4b2e5..a214251 100644 --- a/src/Form/IndexFieldsForm.php +++ b/src/Form/IndexFieldsForm.php @@ -213,7 +213,7 @@ class IndexFieldsForm extends EntityForm { $form['description']['#markup'] = $this->t('

The data type of a field determines how it can be used for searching and filtering. The boost is used to give additional weight to certain fields, e.g. titles or tags.

Whether detailed field types are supported depends on the type of server this index resides on. In any case, fields of type "Fulltext" will always be fulltext-searchable.

'); if ($index->hasValidServer()) { $form['description']['#markup'] .= '

' . $this->t('Check the ' . "server's backend class description for details.", - array(':server-url' => $index->getServer()->toUrl('canonical')->toString())) . '

'; + array(':server-url' => $index->getServerInstance()->toUrl('canonical')->toString())) . '

'; } if ($fields = $index->getFieldsByDatasource(NULL)) { diff --git a/src/Form/IndexForm.php b/src/Form/IndexForm.php index 2bfca64..47d8fec 100644 --- a/src/Form/IndexForm.php +++ b/src/Form/IndexForm.php @@ -262,7 +262,7 @@ class IndexForm extends EntityForm { '#title' => $this->t('Tracker'), '#description' => $this->t('Select the type of tracker which should be used for keeping track of item changes.'), '#options' => $this->getTrackerPluginManager()->getOptionsList(), - '#default_value' => $index->hasValidTracker() ? $index->getTracker()->getPluginId() : key($tracker_options), + '#default_value' => $index->hasValidTracker() ? $index->getTrackerInstance()->getPluginId() : key($tracker_options), '#required' => TRUE, '#disabled' => !$index->isNew(), '#ajax' => array( @@ -313,7 +313,7 @@ class IndexForm extends EntityForm { '#description' => $this->t('Only enabled indexes can be used for indexing and searching. This setting will only take effect if the selected server is also enabled.'), '#default_value' => $index->status(), // Can't enable an index lying on a disabled server or no server at all. - '#disabled' => !$index->status() && (!$index->hasValidServer() || !$index->getServer()->status()), + '#disabled' => !$index->status() && (!$index->hasValidServer() || !$index->getServerInstance()->status()), // @todo This doesn't seem to work and should also hide for disabled // servers. If that works, we can probably remove the last sentence of // the description. @@ -392,7 +392,7 @@ class IndexForm extends EntityForm { */ public function buildTrackerConfigForm(array &$form, FormStateInterface $form_state, IndexInterface $index) { if ($index->hasValidTracker()) { - $tracker = $index->getTracker(); + $tracker = $index->getTrackerInstance(); // @todo Create, use and save SubFormState already here, not only in // validate(). Also, use proper subset of $form for first parameter? if ($config_form = $tracker->buildConfigurationForm(array(), $form_state)) { @@ -484,7 +484,7 @@ class IndexForm extends EntityForm { // form structure). $tracker_id = $form_state->getValue('tracker', NULL); if ($this->originalEntity->getTrackerId() == $tracker_id) { - $tracker = $this->originalEntity->getTracker(); + $tracker = $this->originalEntity->getTrackerInstance(); } else { $tracker = $this->trackerPluginManager->createInstance($tracker_id, array('index' => $this->originalEntity)); @@ -526,15 +526,16 @@ class IndexForm extends EntityForm { $datasources = $form_state->getValue('datasources', array()); /** @var \Drupal\search_api\Datasource\DatasourceInterface[] $datasource_plugins */ $datasource_plugins = $this->originalEntity->getDatasources(FALSE); - $datasource_configuration = array(); + $datasource_settings = array(); foreach ($datasources as $datasource_id) { $datasource = $datasource_plugins[$datasource_id]; $datasource_form = (!empty($form['datasource_configs'][$datasource_id])) ? $form['datasource_configs'][$datasource_id] : array(); $datasource_form_state = new SubFormState($form_state, array('datasource_configs', $datasource_id)); $datasource->submitConfigurationForm($datasource_form, $datasource_form_state); - $datasource_configuration[$datasource_id] = $datasource->getConfiguration(); + + $datasource_settings[$datasource_id] = $datasource; } - $index->set('datasource_configs', $datasource_configuration); + $index->setDatasources($datasource_settings); // Call submitConfigurationForm() for the (possibly new) tracker. // @todo It seems if we change the tracker, we would validate/submit the old @@ -544,7 +545,7 @@ class IndexForm extends EntityForm { // form structure). $tracker_id = $form_state->getValue('tracker', NULL); if ($this->originalEntity->getTrackerId() == $tracker_id) { - $tracker = $this->originalEntity->getTracker(); + $tracker = $this->originalEntity->getTrackerInstance(); } else { $tracker = $this->trackerPluginManager->createInstance($tracker_id, array('index' => $this->originalEntity)); @@ -552,7 +553,7 @@ class IndexForm extends EntityForm { $tracker_form_state = new SubFormState($form_state, array('tracker_config')); $tracker->submitConfigurationForm($form['tracker_config'], $tracker_form_state); - $index->set('tracker_config', $tracker->getConfiguration()); + $index->setTracker($tracker); } /** diff --git a/src/Form/IndexProcessorsForm.php b/src/Form/IndexProcessorsForm.php index d32e5ac..42e6234 100644 --- a/src/Form/IndexProcessorsForm.php +++ b/src/Form/IndexProcessorsForm.php @@ -96,8 +96,8 @@ class IndexProcessorsForm extends EntityForm { $processors_by_stage[$stage] = $this->entity->getProcessorsByStage($stage, FALSE); } - if ($this->entity->getServer()) { - $backend_discouraged_processors = $this->entity->getServer() + if ($this->entity->getServerInstance()) { + $backend_discouraged_processors = $this->entity->getServerInstance() ->getBackend() ->getDiscouragedProcessors(); @@ -112,7 +112,7 @@ class IndexProcessorsForm extends EntityForm { } } - $processor_settings = $this->entity->getProcessorSettings(); + $enabled_processors = $this->entity->getProcessors(); $form['#tree'] = TRUE; $form['#attached']['library'][] = 'search_api/drupal.search_api.index-active-formatters'; @@ -132,7 +132,7 @@ class IndexProcessorsForm extends EntityForm { $form['status'][$processor_id] = array( '#type' => 'checkbox', '#title' => $processor->label(), - '#default_value' => $processor->isLocked() || !empty($processor_settings[$processor_id]), + '#default_value' => $processor->isLocked() || !empty($enabled_processors[$processor_id]), '#description' => $processor->getDescription(), '#attributes' => array( 'class' => array( @@ -171,9 +171,7 @@ class IndexProcessorsForm extends EntityForm { foreach ($processors_by_stage as $stage => $processors) { /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */ foreach ($processors as $processor_id => $processor) { - $weight = isset($processor_settings[$processor_id]['weights'][$stage]) - ? $processor_settings[$processor_id]['weights'][$stage] - : $processor->getDefaultWeight($stage); + $weight = $processor->getDefaultWeight($stage); if ($processor->isHidden()) { $form['processors'][$processor_id]['weights'][$stage] = array( '#type' => 'value', @@ -252,9 +250,13 @@ class IndexProcessorsForm extends EntityForm { $values = $form_state->getValues(); $new_settings = array(); + $old_processors = $this->entity->getProcessors(); + $old_configurations = array(); + foreach ($old_processors as $id => $processor) { + $old_configurations[$id] = $processor->getConfiguration(); + } + // Store processor settings. - // @todo Go through all available processors, enable/disable with method on - // processor plugin to allow reaction. /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */ $processors = $this->entity->getProcessors(FALSE); foreach ($processors as $processor_id => $processor) { @@ -262,27 +264,34 @@ class IndexProcessorsForm extends EntityForm { continue; } $new_settings[$processor_id] = array( - 'processor_id' => $processor_id, - 'weights' => array(), + 'plugin_id' => $processor_id, 'settings' => array(), ); $processor_values = $values['processors'][$processor_id]; - if (!empty($processor_values['weights'])) { - $new_settings[$processor_id]['weights'] = $processor_values['weights']; - } if (isset($form['settings'][$processor_id])) { $processor_form_state = new SubFormState($form_state, array('processors', $processor_id, 'settings')); $processor->submitConfigurationForm($form['settings'][$processor_id], $processor_form_state); $new_settings[$processor_id]['settings'] = $processor->getConfiguration(); + $new_settings[$processor_id]['settings'] += array('index' => $this->entity); + } + if (!empty($processor_values['weights'])) { + $new_settings[$processor_id]['settings']['weights'] = $processor_values['weights']; + } + } + + $new_configurations = array(); + foreach ($new_settings as $plugin_id => $new_processor_settings) { + /** @var \Drupal\search_api\Processor\ProcessorInterface $new_processor */ + $new_processor = $this->processorPluginManager->createInstance($plugin_id, $new_processor_settings['settings']); + if (isset($old_processors[$plugin_id])) { + $this->entity->removeProcessor($plugin_id); } + $this->entity->addProcessor($new_processor); + $new_configurations[$plugin_id] = $new_processor->getConfiguration(); } - // Sort the processors so we won't have unnecessary changes. - ksort($new_settings); - $settings_changed = $new_settings != $this->entity->getProcessorSettings(); - $form_state->set('processors_changed', $settings_changed); - if ($settings_changed) { - $this->entity->setProcessorSettings($new_settings); + if ($old_configurations != $new_configurations) { + $form_state->set('processors_changed', TRUE); } } diff --git a/src/Form/IndexStatusForm.php b/src/Form/IndexStatusForm.php index 640e795..ad42155 100644 --- a/src/Form/IndexStatusForm.php +++ b/src/Form/IndexStatusForm.php @@ -65,7 +65,7 @@ class IndexStatusForm extends FormBase { 'class' => array('container-inline'), ), ); - $has_remaining_items = ($index->getTracker()->getRemainingItemsCount() > 0); + $has_remaining_items = ($index->getTrackerInstance()->getRemainingItemsCount() > 0); $all_value = $this->t('all', array(), array('context' => 'items to index')); $limit = array( '#type' => 'textfield', diff --git a/src/IndexBatchHelper.php b/src/IndexBatchHelper.php index 56c4d3b..144817d 100644 --- a/src/IndexBatchHelper.php +++ b/src/IndexBatchHelper.php @@ -137,7 +137,7 @@ class IndexBatchHelper { // Get the remaining item count. When no valid tracker is available then // the value will be set to zero which will cause the batch process to // stop. - $remaining_item_count = ($index->hasValidTracker() ? $index->getTracker()->getRemainingItemsCount() : 0); + $remaining_item_count = ($index->hasValidTracker() ? $index->getTrackerInstance()->getRemainingItemsCount() : 0); // Check if an explicit limit needs to be used. if ($context['sandbox']['limit'] > -1) { diff --git a/src/IndexInterface.php b/src/IndexInterface.php index a435f35..c5195b6 100644 --- a/src/IndexInterface.php +++ b/src/IndexInterface.php @@ -8,9 +8,12 @@ namespace Drupal\search_api; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\search_api\Datasource\DatasourceInterface; use Drupal\search_api\Item\FieldInterface; +use Drupal\search_api\Processor\ProcessorInterface; use Drupal\search_api\Query\QueryInterface; use Drupal\search_api\Query\ResultSetInterface; +use Drupal\search_api\Tracker\TrackerInterface; /** * Defines the interface for index entities. @@ -125,6 +128,16 @@ interface IndexInterface extends ConfigEntityInterface { public function setOptions(array $options); /** + * Sets this index's datasource plugins. + * + * @param \Drupal\search_api\Datasource\DatasourceInterface[] $datasources + * An array of datasources + * + * @return $this + */ + public function setDatasources(array $datasources); + + /** * Retrieves the IDs of all datasources enabled for this index. * * @return string[] @@ -200,7 +213,17 @@ interface IndexInterface extends ConfigEntityInterface { * @throws \Drupal\search_api\SearchApiException * Thrown if the tracker couldn't be instantiated. */ - public function getTracker(); + public function getTrackerInstance(); + + /** + * Sets the tracker the index uses. + * + * @param \Drupal\search_api\Tracker\TrackerInterface $tracker + * The new tracker for the index. + * + * @return $this + */ + public function setTracker(TrackerInterface $tracker); /** * Determines whether this index is lying on a valid server. @@ -236,7 +259,7 @@ interface IndexInterface extends ConfigEntityInterface { * @throws \Drupal\search_api\SearchApiException * Thrown if the server couldn't be loaded. */ - public function getServer(); + public function getServerInstance(); /** * Sets the server the index is attached to @@ -277,22 +300,24 @@ interface IndexInterface extends ConfigEntityInterface { public function getProcessorsByStage($stage, $only_enabled = TRUE); /** - * Retrieves this index's processor settings. + * Adds a processor to this index. * - * @return array - * An array of processors and their settings. + * @param \Drupal\search_api\Processor\ProcessorInterface $processor + * The processor to be added. + * + * @return $this */ - public function getProcessorSettings(); + public function addProcessor(ProcessorInterface $processor); /** - * Sets this index's processor settings. + * Removes a processor from this index. * - * @param array $processors - * An array of processors and their settings. + * @param string $processor_id + * The ID of the processor to remove. * * @return $this */ - public function setProcessorSettings(array $processors); + public function removeProcessor($processor_id); /** * Preprocesses data items for indexing. @@ -420,24 +445,6 @@ interface IndexInterface extends ConfigEntityInterface { public function getFulltextFields(); /** - * Retrieves this index's field settings. - * - * @return array - * An array of field settings. - */ - public function getFieldSettings(); - - /** - * Sets this index's field settings. - * - * @param array $fields - * An array of field settings. - * - * @return $this - */ - public function setFieldSettings(array $fields); - - /** * Retrieves the properties of one of this index's datasources. * * @param string|null $datasource_id @@ -591,15 +598,6 @@ interface IndexInterface extends ConfigEntityInterface { public function isReindexing(); /** - * Resets the static and stored caches associated with this index. - * - * @param bool $include_stored - * (optional) If set to FALSE, only the static caches will be cleared, the - * stored cache will remain untouched. - */ - public function resetCaches($include_stored = TRUE); - - /** * Creates a query object for this index. * * @param array $options diff --git a/src/Plugin/search_api/datasource/ContentEntity.php b/src/Plugin/search_api/datasource/ContentEntity.php index 570085c..8116722 100644 --- a/src/Plugin/search_api/datasource/ContentEntity.php +++ b/src/Plugin/search_api/datasource/ContentEntity.php @@ -940,7 +940,7 @@ class ContentEntity extends DatasourcePluginBase { $entity_bundle = $entity->bundle(); $index_names = \Drupal::entityQuery('search_api_index') - ->condition('datasources.*', $datasource_id) + ->condition('datasource_settings.*.plugin_id', $datasource_id) ->execute(); if (!$index_names) { diff --git a/src/Plugin/views/argument/SearchApiMoreLikeThis.php b/src/Plugin/views/argument/SearchApiMoreLikeThis.php index f39c8b0..bfe03e3 100644 --- a/src/Plugin/views/argument/SearchApiMoreLikeThis.php +++ b/src/Plugin/views/argument/SearchApiMoreLikeThis.php @@ -69,7 +69,7 @@ class SearchApiMoreLikeThis extends SearchApiArgument { */ public function query($group_by = FALSE) { try { - $server = $this->query->getIndex()->getServer(); + $server = $this->query->getIndex()->getServerInstance(); if (!$server->supportsFeature('search_api_mlt')) { $backend_id = $server->getBackendId(); \Drupal::logger('search_api')->error('The search backend "@backend_id" does not offer "More like this" functionality.', diff --git a/src/Query/Query.php b/src/Query/Query.php index eb22371..53bbe60 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -318,7 +318,7 @@ class Query implements QueryInterface { $this->preExecute(); // Execute query. - $response = $this->index->getServer()->search($this); + $response = $this->index->getServerInstance()->search($this); // Postprocess the search results. $this->postExecute($response); diff --git a/src/Task/IndexTaskManager.php b/src/Task/IndexTaskManager.php index db1b5b8..4cfafcb 100644 --- a/src/Task/IndexTaskManager.php +++ b/src/Task/IndexTaskManager.php @@ -99,7 +99,7 @@ class IndexTaskManager implements IndexTaskManagerInterface { $item_ids[] = Utility::createCombinedId($datasource_id, $raw_id); } $added = count($item_ids); - $index->getTracker()->trackItemsInserted($item_ids); + $index->getTrackerInstance()->trackItemsInserted($item_ids); } } } @@ -195,7 +195,7 @@ class IndexTaskManager implements IndexTaskManagerInterface { if (!isset($datasource_ids)) { $this->state->delete($this->getIndexStateKey($index)); if ($valid_tracker) { - $index->getTracker()->trackAllItemsDeleted(); + $index->getTrackerInstance()->trackAllItemsDeleted(); } return; } @@ -211,7 +211,7 @@ class IndexTaskManager implements IndexTaskManagerInterface { foreach ($datasource_ids as $datasource_id) { unset($index_state['pages'][$datasource_id]); if ($valid_tracker) { - $index->getTracker()->trackAllItemsDeleted($datasource_id); + $index->getTrackerInstance()->trackAllItemsDeleted($datasource_id); } } diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php index a14f24d..bf653c4 100644 --- a/src/Tests/IntegrationTest.php +++ b/src/Tests/IntegrationTest.php @@ -98,6 +98,55 @@ class IntegrationTest extends WebTestBase { } /** + * Tests what happens when an index has an integer as id/label. + * + * This needs to be in a separate test because we want to test the content + * tracking behavior as well as the fields / processors editing and adding + * without messing with the other index. This test also makes sure that the + * server also has an integer as id/label. + */ + public function testIntegerIndex() { + $this->drupalLogin($this->adminUser); + $this->getTestServer(789, 456); + + $this->drupalCreateNode(array('type' => 'article')); + $this->drupalCreateNode(array('type' => 'article')); + + $this->drupalGet('admin/config/search/search-api/add-index'); + + $this->indexId = 123; + $edit = array( + 'name' => $this->indexId, + 'id' => $this->indexId, + 'status' => 1, + 'description' => 'test Index:: 123~', + 'server' => 456, + 'datasources[]' => array('entity:node'), + ); + $this->drupalPostForm(NULL, $edit, $this->t('Save')); + $this->assertResponse(200); + $this->assertText($this->t('The index was successfully saved.')); + $this->assertText($this->t('Successfully tracked @count items for this index.', array('@count' => 2))); + $this->assertEqual(2, $this->countTrackedItems()); + + $this->enableAllProcessors(); + $this->checkFieldLabels(); + + $this->addFieldsToIndex(); + $this->removeFieldsFromIndex(); + + $this->configureFilter(); + $this->configureFilterPage(); + $this->checkProcessorChanges(); + $this->changeProcessorFieldBoost(); + + $this->setReadOnly(); + $this->disableEnableIndex(); + $this->changeIndexDatasource(); + $this->changeIndexServer(); + } + + /** * Tests creating a search server via the UI. */ protected function createServer($server_id = '_test_server') { @@ -382,7 +431,7 @@ class IntegrationTest extends WebTestBase { * The number of tracked items in the test index. */ protected function countTrackedItems() { - return $this->getIndex()->getTracker()->getTotalItemsCount(); + return $this->getIndex()->getTrackerInstance()->getTotalItemsCount(); } /** @@ -392,7 +441,7 @@ class IntegrationTest extends WebTestBase { * The number of unindexed items in the test index. */ protected function countRemainingItems() { - return $this->getIndex()->getTracker()->getRemainingItemsCount(); + return $this->getIndex()->getTrackerInstance()->getRemainingItemsCount(); } /** @@ -454,10 +503,6 @@ class IntegrationTest extends WebTestBase { $this->drupalPostForm(NULL, $edit, $this->t('Save and continue')); $this->drupalPostForm(NULL, array(), $this->t('Save field settings')); - // @todo This should not be necessary, the field cache should be invalidated - // automatically when available fields change. See #2637032. - $this->getIndex()->resetCaches(); - $url_options['query']['datasource'] = 'entity:node'; $this->drupalGet($this->getIndexPath('fields/add'), $url_options); $this->assertHtmlEscaped($field_name); @@ -723,7 +768,7 @@ class IntegrationTest extends WebTestBase { $this->drupalPostForm($index_path, array(), $this->t('Index now')); - $remaining_after = $index->getTracker()->getRemainingItemsCount(); + $remaining_after = $index->getTrackerInstance()->getRemainingItemsCount(); $this->assertEqual(0, $remaining_after, 'Items were indexed after removing the "read only" flag.'); } diff --git a/src/Tests/LanguageIntegrationTest.php b/src/Tests/LanguageIntegrationTest.php index 92c88df..5a4a428 100644 --- a/src/Tests/LanguageIntegrationTest.php +++ b/src/Tests/LanguageIntegrationTest.php @@ -120,6 +120,6 @@ class LanguageIntegrationTest extends WebTestBase { protected function countTrackedItems() { /** @var \Drupal\search_api\IndexInterface $index */ $index = Index::load($this->indexId); - return $index->getTracker()->getTotalItemsCount(); + return $index->getTrackerInstance()->getTotalItemsCount(); } } diff --git a/src/Tests/Processor/ContentAccessTest.php b/src/Tests/Processor/ContentAccessTest.php index b301cd4..644db5f 100644 --- a/src/Tests/Processor/ContentAccessTest.php +++ b/src/Tests/Processor/ContentAccessTest.php @@ -109,7 +109,12 @@ class ContentAccessTest extends ProcessorTestBase { $this->nodes[2]->save(); // Also index users, to verify that they are unaffected by the processor. - $this->index->set('datasources', array('entity:comment', 'entity:node', 'entity:user')); + $manager = \Drupal::getContainer() + ->get('plugin.manager.search_api.datasource'); + $datasources['entity:comment'] = $manager->createInstance('entity:comment', array('index' => $this->index)); + $datasources['entity:node'] = $manager->createInstance('entity:node', array('index' => $this->index)); + $datasources['entity:user'] = $manager->createInstance('entity:user', array('index' => $this->index)); + $this->index->setDatasources($datasources); $this->index->save(); \Drupal::getContainer()->get('search_api.index_task_manager')->addItemsAll($this->index); @@ -125,6 +130,8 @@ class ContentAccessTest extends ProcessorTestBase { user_role_grant_permissions('anonymous', array('access content', 'access comments')); $this->index->reindex(); $this->index->indexItems(); + $this->assertEqual(5, $this->index->getTrackerInstance()->getIndexedItemsCount(), '5 items indexed, as expected.'); + $query = Utility::createQuery($this->index); $result = $query->execute(); @@ -138,6 +145,8 @@ class ContentAccessTest extends ProcessorTestBase { user_role_grant_permissions('anonymous', array('access comments')); $this->index->reindex(); $this->index->indexItems(); + $this->assertEqual(5, $this->index->getTrackerInstance()->getIndexedItemsCount(), '5 items indexed, as expected.'); + $query = Utility::createQuery($this->index); $result = $query->execute(); @@ -154,6 +163,7 @@ class ContentAccessTest extends ProcessorTestBase { $this->nodes[3] = Node::create(array('status' => NODE_NOT_PUBLISHED, 'type' => 'page', 'title' => 'foo', 'uid' => 2)); $this->nodes[3]->save(); $this->index->indexItems(); + $this->assertEqual(7, $this->index->getTrackerInstance()->getIndexedItemsCount(), '7 items indexed, as expected.'); $query = Utility::createQuery($this->index); $query->setOption('search_api_access_account', $authenticated_user); diff --git a/src/Tests/Processor/ProcessorIntegrationTest.php b/src/Tests/Processor/ProcessorIntegrationTest.php index ad617c8..4c81fcf 100644 --- a/src/Tests/Processor/ProcessorIntegrationTest.php +++ b/src/Tests/Processor/ProcessorIntegrationTest.php @@ -32,7 +32,12 @@ class ProcessorIntegrationTest extends WebTestBase { 'name' => 'Test index', 'id' => $this->indexId, 'status' => 1, - 'datasources' => array('entity:node'), + 'datasource_settings' => array( + 'entity:node' => array( + 'plugin_id' => 'entity:node', + 'settings' => array(), + ), + ), ))->save(); } @@ -43,18 +48,92 @@ class ProcessorIntegrationTest extends WebTestBase { * avoid the overhead of having one test per processor. */ public function testProcessorIntegration() { + // By default, the add_url and language processors are already enabled. + $enabled = array('language', 'add_url'); + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkContentAccessIntegration(); + $enabled[] = 'content_access'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkHighlightIntegration(); + $enabled[] = 'highlight'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkHtmlFilterIntegration(); + $enabled[] = 'html_filter'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkIgnoreCaseIntegration(); + $enabled[] = 'ignorecase'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkIgnoreCharactersIntegration(); - $this->checkLanguageIntegration(); + $enabled[] = 'ignore_character'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkNodeStatusIntegration(); + $enabled[] = 'node_status'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkRenderedItemIntegration(); + $enabled[] = 'rendered_item'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkStopWordsIntegration(); + $enabled[] = 'stopwords'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkTokenizerIntegration(); + $enabled[] = 'tokenizer'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkTransliterationIntegration(); + $enabled[] = 'transliteration'; + sort($enabled); + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + + // The 'language' and 'add_url' processors are are not available to be + // removed because they are locked processors. + $actual_processors = array_keys($this->loadIndex()->getProcessors()); + sort($actual_processors); + $this->assertEqual($enabled, $actual_processors); + $this->checkLanguageIntegration(); + $this->assertEqual($enabled, $actual_processors); $this->checkUrlFieldIntegration(); + $this->assertEqual($enabled, $actual_processors); } /** @@ -168,10 +247,11 @@ class ProcessorIntegrationTest extends WebTestBase { */ public function checkLanguageIntegration() { $index = $this->loadIndex(); - $processors = $index->getProcessorSettings(); + $processors = $index->getProcessors(); $this->assertTrue(!empty($processors['language']), 'The "language" processor is enabled by default.'); - unset($processors['language']); - $index->setProcessorSettings($processors)->save(); + $index->removeProcessor('language'); + $index->save(); + $processors = $this->loadIndex()->getProcessors(); $this->assertTrue(!empty($processors['language']), 'The "language" processor cannot be disabled.'); } @@ -240,10 +320,11 @@ class ProcessorIntegrationTest extends WebTestBase { */ public function checkUrlFieldIntegration() { $index = $this->loadIndex(); - $processors = $index->getProcessorSettings(); + $processors = $index->getProcessors(); $this->assertTrue(!empty($processors['add_url']), 'The "Add URL" processor is enabled by default.'); - unset($processors['add_url']); - $index->setProcessorSettings($processors)->save(); + $index->removeProcessor('add_url'); + $index->save(); + $processors = $this->loadIndex()->getProcessors(); $this->assertTrue(!empty($processors['add_url']), 'The "Add URL" processor cannot be disabled.'); } diff --git a/src/Tests/Processor/ProcessorTestBase.php b/src/Tests/Processor/ProcessorTestBase.php index 2809c31..17d4932 100644 --- a/src/Tests/Processor/ProcessorTestBase.php +++ b/src/Tests/Processor/ProcessorTestBase.php @@ -9,6 +9,7 @@ namespace Drupal\search_api\Tests\Processor; use Drupal\search_api\Entity\Index; use Drupal\search_api\Entity\Server; +use Drupal\search_api\Item\Field; use Drupal\search_api\Utility; use Drupal\system\Tests\Entity\EntityUnitTestBase; @@ -87,37 +88,41 @@ abstract class ProcessorTestBase extends EntityUnitTestBase { 'id' => 'index', 'name' => 'Index name', 'status' => TRUE, - 'datasources' => array('entity:comment', 'entity:node'), + 'datasource_settings' => array( + 'entity:comment' => array( + 'plugin_id' => 'entity:comment', + 'settings' => array(), + ), + 'entity:node' => array( + 'plugin_id' => 'entity:node', + 'settings' => array(), + ), + ), 'server' => 'server', 'tracker' => 'default', )); $this->index->setServer($this->server); - $this->index->setFieldSettings(array( - 'subject' => array( - 'label' => 'Subject', - 'type' => 'text', - 'datasource_id' => 'entity:comment', - 'property_path' => 'subject', - ), - 'title' => array( - 'label' => 'Title', - 'type' => 'text', - 'datasource_id' => 'entity:node', - 'property_path' => 'title', - ), - )); - if ($processor) { - $this->index->setProcessorSettings(array( - $processor => array( - 'processor_id' => $processor, - 'weights' => array(), - 'settings' => array(), - ), - )); + $field_subject = new Field($this->index, 'subject'); + $field_subject->setType('text'); + $field_subject->setPropertyPath('subject'); + $field_subject->setDatasourceId('entity:comment'); + $field_subject->setLabel('Subject'); + + $field_title = new Field($this->index, 'title'); + $field_title->setType('text'); + $field_title->setPropertyPath('title'); + $field_title->setDatasourceId('entity:node'); + $field_title->setLabel('Title'); + + $this->index->addField($field_subject); + $this->index->addField($field_title); + + if ($processor) { /** @var \Drupal\search_api\Processor\ProcessorPluginManager $plugin_manager */ $plugin_manager = \Drupal::service('plugin.manager.search_api.processor'); $this->processor = $plugin_manager->createInstance($processor, array('index' => $this->index)); + $this->index->addProcessor($this->processor); } $this->index->save(); } diff --git a/src/Tests/Processor/RenderedItemTest.php b/src/Tests/Processor/RenderedItemTest.php index 8a920e4..4b561fc 100644 --- a/src/Tests/Processor/RenderedItemTest.php +++ b/src/Tests/Processor/RenderedItemTest.php @@ -115,6 +115,35 @@ class RenderedItemTest extends ProcessorTestBase { } /** + * Tests that the processor is added correctly. + */ + public function testAddProcessor() { + $processors = $this->index->getProcessors(); + $this->assertTrue( + array_key_exists('rendered_item', $processors), + 'Processor successfully added.' + ); + + $items = array(); + foreach ($this->nodes as $node) { + $items[] = array( + 'datasource' => 'entity:node', + 'item' => $node->getTypedData(), + 'item_id' => $node->id(), + 'text' => 'node text' . $node->id(), + ); + } + $items = $this->generateItems($items); + + foreach ($items as $item) { + $this->assertTrue( + array_key_exists('rendered_item', $item->getFields()), + 'Field successfully added.' + ); + } + } + + /** * Tests whether the rendered_item field is correctly filled by the processor. */ public function testPreprocessIndexItems() { diff --git a/src/Tests/WebTestBase.php b/src/Tests/WebTestBase.php index 8b85ea6..4dd9113 100644 --- a/src/Tests/WebTestBase.php +++ b/src/Tests/WebTestBase.php @@ -169,7 +169,12 @@ abstract class WebTestBase extends SimpletestWebTestBase { 'name' => $name, 'description' => $name . ' description', 'server' => $server_id, - 'datasources' => array($datasource_id), + 'datasource_settings' => array( + $datasource_id => array( + 'plugin_id' => $datasource_id, + 'settings' => array() + ) + ), )); $index->save(); $this->indexId = $index->id(); diff --git a/src/UnsavedIndexConfiguration.php b/src/UnsavedIndexConfiguration.php index 8fe07be..1f1045e 100644 --- a/src/UnsavedIndexConfiguration.php +++ b/src/UnsavedIndexConfiguration.php @@ -249,8 +249,8 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI /** * {@inheritdoc} */ - public function getTracker() { - return $this->entity->getTracker(); + public function getTrackerInstance() { + return $this->entity->getTrackerInstance(); } /** @@ -277,8 +277,8 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI /** * {@inheritdoc} */ - public function getServer() { - return $this->entity->getServer(); + public function getServerInstance() { + return $this->entity->getServerInstance(); } /** @@ -431,13 +431,6 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI /** * {@inheritdoc} */ - public function resetCaches($include_stored = TRUE) { - return $this->entity->resetCaches($include_stored); - } - - /** - * {@inheritdoc} - */ public function query(array $options = array()) { return $this->entity->query($options); } diff --git a/src/Utility.php b/src/Utility.php index aed1a96..3674331 100644 --- a/src/Utility.php +++ b/src/Utility.php @@ -154,7 +154,7 @@ class Utility { if (empty(static::$dataTypeFallbackMapping[$index_id])) { $server = NULL; try { - $server = $index->getServer(); + $server = $index->getServerInstance(); } catch (SearchApiException $e) { // If the server isn't available, just ignore it here and return all diff --git a/tests/search_api_test_db/config/install/search_api.index.database_search_index.yml b/tests/search_api_test_db/config/install/search_api.index.database_search_index.yml index 4f42d29..e6c886a 100644 --- a/tests/search_api_test_db/config/install/search_api.index.database_search_index.yml +++ b/tests/search_api_test_db/config/install/search_api.index.database_search_index.yml @@ -2,7 +2,7 @@ id: database_search_index name: 'Test index' description: 'An index used for testing' read_only: false -fields: +field_settings: id: label: ID type: integer @@ -40,25 +40,28 @@ fields: property_path: search_api_language index_locked: true type_locked: true -processors: +processor_settings: add_url: - processor_id: add_url - weights: - preprocess_index: -30 - settings: { } + plugin_id: add_url + settings: + weights: + preprocess_index: -30 language: - processor_id: language - weights: - preprocess_index: -50 - settings: { } + plugin_id: language + settings: + weights: + preprocess_index: -50 options: cron_limit: -1 index_directly: false -datasources: - - 'entity:entity_test' -datasource_configs: { } -tracker: default -tracker_config: { } +datasource_settings: + 'entity:entity_test': + plugin_id: 'entity:entity_test' + settings: { } +tracker_settings: + 'default': + plugin_id: default + settings: { } server: database_search_server status: true langcode: en diff --git a/tests/src/Kernel/CliTest.php b/tests/src/Kernel/CliTest.php index 70fd5cc..655a7a6 100644 --- a/tests/src/Kernel/CliTest.php +++ b/tests/src/Kernel/CliTest.php @@ -94,18 +94,23 @@ class CliTest extends KernelTestBase { 'name' => 'Test index', 'id' => 'index', 'status' => 1, - 'datasources' => array('entity:entity_test'), + 'datasource_settings' => array( + 'entity:entity_test' => array( + 'plugin_id' => 'entity:entity_test', + 'settings' => array(), + ), + ), 'tracker' => 'default', 'server' => $this->server->id(), 'options' => array('index_directly' => TRUE), )); $index->save(); - $total_items = $index->getTracker()->getTotalItemsCount(); - $indexed_items = $index->getTracker()->getIndexedItemsCount(); + $total_items = $index->getTrackerInstance()->getTotalItemsCount(); + $indexed_items = $index->getTrackerInstance()->getIndexedItemsCount(); - $this->assertEquals($total_items, 2, 'The 2 items are tracked.'); - $this->assertEquals($indexed_items, 0, 'No items are indexed'); + $this->assertEquals(2, $total_items,'The 2 items are tracked.'); + $this->assertEquals(0, $indexed_items, 'No items are indexed'); EntityTest::create(array( 'name' => 'foo bar baz föö smile', @@ -122,11 +127,11 @@ class CliTest extends KernelTestBase { 'category' => 'item_category' ))->save(); - $total_items = $index->getTracker()->getTotalItemsCount(); - $indexed_items = $index->getTracker()->getIndexedItemsCount(); + $total_items = $index->getTrackerInstance()->getTotalItemsCount(); + $indexed_items = $index->getTrackerInstance()->getIndexedItemsCount(); - $this->assertEquals($total_items, 4, 'All 4 items are tracked.'); - $this->assertEquals($indexed_items, 2, '2 items are indexed'); + $this->assertEquals(4, $total_items, 'All 4 items are tracked.'); + $this->assertEquals(2, $indexed_items, '2 items are indexed'); } } diff --git a/tests/src/Kernel/CustomDataTypesTest.php b/tests/src/Kernel/CustomDataTypesTest.php index 56b9647..066ae40 100644 --- a/tests/src/Kernel/CustomDataTypesTest.php +++ b/tests/src/Kernel/CustomDataTypesTest.php @@ -116,30 +116,38 @@ class CustomDataTypesTest extends KernelTestBase { $item = $this->index->loadItem('entity:entity_test/1:en'); $item = Utility::createItemFromObject($this->index, $item, 'entity:entity_test/1:en'); - $name_field = $item->getField('name'); + $name_field = $item->getField('name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); + $label = $name_field->getLabel(); $this->assertEquals($original_value, $processed_value, 'The processed value matches the original value'); $this->assertEquals($original_type, $processed_type, 'The processed type matches the original type.'); + $this->assertEquals('Name', $label, 'The label is correctly set.'); // Reset the fields on the item and change to the supported data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); - $this->index->getField('name')->setType('search_api_test_data_type'); - $name_field = $item->getField('name'); + $field = $this->index->getField('name') + ->setType('search_api_test_data_type') + ->setLabel("Test"); + $this->index->addField($field); + $name_field = $item->getField('name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); $this->assertEquals($original_value, $processed_value, 'The processed value matches the original value'); $this->assertEquals('search_api_test_data_type', $processed_type, 'The processed type matches the new type.'); + $this->assertEquals('Test', $name_field->getLabel(), 'The label is correctly set.'); // Reset the fields on the item and change to the non-supported data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); - $this->index->getField('name')->setType('search_api_unsupported_test_data_type'); + $field = $this->index->getField('name') + ->setType('search_api_unsupported_test_data_type'); + $this->index->addField($field); $name_field = $item->getField('name'); $processed_value = $name_field->getValues()[0]; @@ -151,7 +159,9 @@ class CustomDataTypesTest extends KernelTestBase { // Reset the fields on the item and change to the data altering data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); - $this->index->getField('name')->setType('search_api_altering_test_data_type'); + $field = $this->index->getField('name') + ->setType('search_api_altering_test_data_type'); + $this->index->addField($field); $name_field = $item->getField('name'); $processed_value = $name_field->getValues()[0]; diff --git a/tests/src/Kernel/DependencyRemovalTest.php b/tests/src/Kernel/DependencyRemovalTest.php index 9245486..e6c460c 100644 --- a/tests/src/Kernel/DependencyRemovalTest.php +++ b/tests/src/Kernel/DependencyRemovalTest.php @@ -37,6 +37,7 @@ class DependencyRemovalTest extends KernelTestBase { */ public static $modules = array( 'user', + 'system', 'search_api', 'search_api_test_backend', 'search_api_test_dependencies', @@ -48,6 +49,8 @@ class DependencyRemovalTest extends KernelTestBase { public function setUp() { parent::setUp(); + $this->installSchema('system', 'key_value_expire'); + // The server tasks manager is needed when removing a server. $mock = $this->getMock('Drupal\search_api\Task\ServerTaskManagerInterface'); $this->container->set('search_api.server_task_manager', $mock); @@ -57,9 +60,17 @@ class DependencyRemovalTest extends KernelTestBase { $this->index = Index::create(array( 'id' => 'test_index', 'name' => 'Test index', - 'tracker' => 'default', - 'datasources' => array( - 'entity:user', + 'tracker_settings' => array( + 'default' => array( + 'plugin_id' => 'default', + 'settings' => array() + ) + ), + 'datasource_settings' => array( + 'entity:user' => array( + 'plugin_id' => 'entity:user', + 'settings' => array(), + ) ), )); @@ -111,7 +122,7 @@ class DependencyRemovalTest extends KernelTestBase { // Set the server on the index and save that, too. However, we don't want // the index enabled, since that would lead to all kinds of overhead which // is completely irrelevant for this test. - $this->index->set('server', $server->id()); + $this->index->setServer($server); $this->index->disable(); $this->index->save(); @@ -173,17 +184,17 @@ class DependencyRemovalTest extends KernelTestBase { // server. $dependency_key = $this->dependency->getConfigDependencyKey(); $dependency_name = $this->dependency->getConfigDependencyName(); - $this->index->set('datasources', array( - 'entity:user', + + // Also index users, to verify that they are unaffected by the processor. + $manager = \Drupal::getContainer() + ->get('plugin.manager.search_api.datasource'); + $datasources['entity:user'] = $manager->createInstance('entity:user', array('index' => $this->index)); + $datasources['search_api_test_dependencies'] = $manager->createInstance( 'search_api_test_dependencies', - )); - $this->index->set('datasource_configs', array( - 'search_api_test_dependencies' => array( - $dependency_key => array( - $dependency_name, - ), - ), - )); + array($dependency_key => array($dependency_name), + 'index' => $this->index)); + $this->index->setDatasources($datasources); + $this->index->save(); // Check the dependencies were calculated correctly. @@ -210,15 +221,13 @@ class DependencyRemovalTest extends KernelTestBase { // Depending on whether the plugin should have removed the dependency or // not, make sure the right action was taken. - $datasources = $this->index->get('datasources'); - $datasource_configs = $this->index->get('datasource_configs'); + $datasources = $this->index->getDatasources(); if ($remove_dependency) { - $this->assertContains('search_api_test_dependencies', $datasources, 'Datasource not removed'); - $this->assertEmpty($datasource_configs['search_api_test_dependencies'], 'Datasource settings adapted'); + $this->assertArrayHasKey('search_api_test_dependencies', $datasources, 'Datasource not removed'); + $this->assertEmpty($datasources['search_api_test_dependencies']->getConfiguration(), 'Datasource settings adapted'); } else { - $this->assertNotContains('search_api_test_dependencies', $datasources, 'Datasource removed'); - $this->assertArrayNotHasKey('search_api_test_dependencies', $datasource_configs, 'Datasource config removed'); + $this->assertArrayNotHasKey('search_api_test_dependencies', $datasources, 'Datasource removed'); } } @@ -231,16 +240,14 @@ class DependencyRemovalTest extends KernelTestBase { // server. $dependency_key = $this->dependency->getConfigDependencyKey(); $dependency_name = $this->dependency->getConfigDependencyName(); - $this->index->set('datasources', array( - 'search_api_test_dependencies', - )); - $this->index->set('datasource_configs', array( - 'search_api_test_dependencies' => array( - $dependency_key => array( - $dependency_name, - ), - ), - )); + $datasources['search_api_test_dependencies'] = \Drupal::getContainer() + ->get('plugin.manager.search_api.datasource') + ->createInstance( + 'search_api_test_dependencies', + array($dependency_key => array($dependency_name)) + ); + $this->index->setDatasources($datasources); + $this->index->save(); // Since in this test the index will be removed, we need a mock key/value @@ -275,16 +282,14 @@ class DependencyRemovalTest extends KernelTestBase { // server. $dependency_key = $this->dependency->getConfigDependencyKey(); $dependency_name = $this->dependency->getConfigDependencyName(); - $this->index->set('processors', array( - 'search_api_test_dependencies' => array( - 'processor_id' => 'search_api_test_dependencies', - 'settings' => array( - $dependency_key => array( - $dependency_name, - ), - ), - ), - )); + + $processor = \Drupal::getContainer() + ->get('plugin.manager.search_api.processor') + ->createInstance( + 'search_api_test_dependencies', + array($dependency_key => array($dependency_name)) + ); + $this->index->addProcessor($processor); $this->index->save(); // Check the dependencies were calculated correctly. @@ -311,10 +316,10 @@ class DependencyRemovalTest extends KernelTestBase { // Depending on whether the plugin should have removed the dependency or // not, make sure the right action was taken. - $processors = $this->index->get('processors'); + $processors = $this->index->getProcessors(); if ($remove_dependency) { $this->assertArrayHasKey('search_api_test_dependencies', $processors, 'Processor not removed'); - $this->assertEmpty($processors['search_api_test_dependencies']['settings'], 'Processor settings adapted'); + $this->assertEmpty($processors['search_api_test_dependencies']->getConfiguration(), 'Processor settings adapted'); } else { $this->assertArrayNotHasKey('search_api_test_dependencies', $processors, 'Processor removed'); @@ -336,12 +341,15 @@ class DependencyRemovalTest extends KernelTestBase { // server. $dependency_key = $this->dependency->getConfigDependencyKey(); $dependency_name = $this->dependency->getConfigDependencyName(); - $this->index->set('tracker', 'search_api_test_dependencies'); - $this->index->set('tracker_config', array( - $dependency_key => array( - $dependency_name, - ), - )); + + $tracker = \Drupal::getContainer() + ->get('plugin.manager.search_api.tracker') + ->createInstance('search_api_test_dependencies', array( + $dependency_key => array( + $dependency_name, + ), + )); + $this->index->setTracker($tracker); $this->index->save(); // Check the dependencies were calculated correctly. @@ -375,8 +383,9 @@ class DependencyRemovalTest extends KernelTestBase { // Depending on whether the plugin should have removed the dependency or // not, make sure the right action was taken. - $tracker = $this->index->get('tracker'); - $tracker_config = $this->index->get('tracker_config'); + $tracker_instance = $this->index->getTrackerInstance(); + $tracker = $tracker_instance->getPluginId(); + $tracker_config = $tracker_instance->getConfiguration(); if ($remove_dependency) { $this->assertEquals('search_api_test_dependencies', $tracker, 'Tracker not reset'); $this->assertEmpty($tracker_config, 'Tracker settings adapted'); @@ -392,17 +401,24 @@ class DependencyRemovalTest extends KernelTestBase { */ public function testModuleDependency() { // Test with all types of plugins at once. - $this->index->set('datasources', array( - 'entity:user', - 'search_api_test_dependencies', - )); - $this->index->set('processors', array( - 'search_api_test_dependencies' => array( - 'processor_id' => 'search_api_test_dependencies', - 'settings' => array(), - ), - )); - $this->index->set('tracker', 'search_api_test_dependencies'); + $datasources['search_api_test_dependencies'] = \Drupal::getContainer() + ->get('plugin.manager.search_api.datasource') + ->createInstance('search_api_test_dependencies', array('index' => $this->index)); + $datasources['entity:user'] = \Drupal::getContainer() + ->get('plugin.manager.search_api.datasource') + ->createInstance('entity:user', array('index' => $this->index)); + $this->index->setDatasources($datasources); + + $processor = \Drupal::getContainer() + ->get('plugin.manager.search_api.processor') + ->createInstance('search_api_test_dependencies'); + $this->index->addProcessor($processor); + + $tracker = \Drupal::getContainer() + ->get('plugin.manager.search_api.tracker') + ->createInstance('search_api_test_dependencies'); + $this->index->setTracker($tracker); + $this->index->save(); // Check the dependencies were calculated correctly. @@ -439,6 +455,16 @@ class DependencyRemovalTest extends KernelTestBase { /** * Data provider for this class's test methods. * + * If $remove_dependency is TRUE, in Plugin::onDependencyRemoval() it clears + * its configuration (and thus its dependency, in those test plugins) and + * returns TRUE, which the index will take as "all OK, dependency removed" and + * leave the plugin where it is, only with updated configuration. + * + * If $remove_dependency is FALSE, Plugin::onDependencyRemoval() will do + * nothing and just return FALSE, the index says "oh, that plugin still has + * that removed dependency, so I should better remove the plugin" and the + * plugin gets removed. + * * @return array * An array of argument arrays for this class's test methods. */ diff --git a/tests/src/Kernel/IndexStorageTest.php b/tests/src/Kernel/IndexStorageTest.php index 0e89a46..a682072 100644 --- a/tests/src/Kernel/IndexStorageTest.php +++ b/tests/src/Kernel/IndexStorageTest.php @@ -40,7 +40,9 @@ class IndexStorageTest extends KernelTestBase { $this->installSchema('system', 'key_value_expire'); - $this->storage = $this->container->get('entity_type.manager')->getStorage('search_api_index'); + $this->storage = $this->container + ->get('entity_type.manager') + ->getStorage('search_api_index'); } /** @@ -60,11 +62,10 @@ class IndexStorageTest extends KernelTestBase { * @return \Drupal\search_api\IndexInterface * The newly created search index. */ - public function indexCreate() { + protected function indexCreate() { $indexData = array( 'id' => 'test', - 'name' => 'Index test name', - 'tracker' => 'default', + 'name' => 'Index test name' ); $index = $this->storage->create($indexData); @@ -80,7 +81,7 @@ class IndexStorageTest extends KernelTestBase { * @param \Drupal\search_api\IndexInterface $index * The index used for the test. */ - public function indexLoad(IndexInterface $index) { + protected function indexLoad(IndexInterface $index) { $loaded_index = $this->storage->load($index->id()); $this->assertSame($index->label(), $loaded_index->label()); } @@ -91,7 +92,7 @@ class IndexStorageTest extends KernelTestBase { * @param \Drupal\search_api\IndexInterface $index * The index used for the test. */ - public function indexDelete(IndexInterface $index) { + protected function indexDelete(IndexInterface $index) { $this->storage->delete(array($index)); $loaded_index = $this->storage->load($index->id()); $this->assertNull($loaded_index); diff --git a/tests/src/Kernel/LanguageKernelTest.php b/tests/src/Kernel/LanguageKernelTest.php index 671a957..75c8bc6 100644 --- a/tests/src/Kernel/LanguageKernelTest.php +++ b/tests/src/Kernel/LanguageKernelTest.php @@ -108,8 +108,18 @@ class LanguageKernelTest extends KernelTestBase { 'name' => 'Test Index', 'id' => 'test_index', 'status' => 1, - 'datasources' => array('entity:' . $this->testEntityTypeId), - 'tracker' => 'default', + 'datasource_settings' => array( + 'entity:' . $this->testEntityTypeId => array( + 'plugin_id' => 'entity:' . $this->testEntityTypeId, + 'settings' => array(), + ), + ), + 'tracker_settings' => array( + 'default' => array( + 'plugin_id' => 'default', + 'settings' => array(), + ) + ), 'server' => $this->server->id(), 'options' => array('index_directly' => FALSE), )); @@ -153,10 +163,10 @@ class LanguageKernelTest extends KernelTestBase { $this->assertEquals($expected, $datasource_item_ids, 'Datasource returns correct item ids.'); // Test indexing the new entity. - $this->assertEquals(0, $this->index->getTracker()->getIndexedItemsCount(), 'The index is empty.'); - $this->assertEquals(2, $this->index->getTracker()->getTotalItemsCount(), 'There are two items to be indexed.'); + $this->assertEquals(0, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'The index is empty.'); + $this->assertEquals(2, $this->index->getTrackerInstance()->getTotalItemsCount(), 'There are two items to be indexed.'); $this->index->indexItems(); - $this->assertEquals(2, $this->index->getTracker()->getIndexedItemsCount(), 'Two items have been indexed.'); + $this->assertEquals(2, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'Two items have been indexed.'); // Now, make the first entity language-specific by assigning a language. $default_langcode = $this->langcodes[0]; @@ -175,8 +185,8 @@ class LanguageKernelTest extends KernelTestBase { $this->assertEquals($expected, $datasource_item_ids, 'Datasource returns correct item ids.'); // Test that the index needs to be updated. - $this->assertEquals(1, $this->index->getTracker()->getIndexedItemsCount(), 'The updated item needs to be reindexed.'); - $this->assertEquals(2, $this->index->getTracker()->getTotalItemsCount(), 'There are two items in total.'); + $this->assertEquals(1, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'The updated item needs to be reindexed.'); + $this->assertEquals(2, $this->index->getTrackerInstance()->getTotalItemsCount(), 'There are two items in total.'); // Set two translations for the first entity and test that the datasource // returns three separate item IDs, one for each translation. @@ -197,8 +207,8 @@ class LanguageKernelTest extends KernelTestBase { $this->assertEquals($expected, $datasource_item_ids, 'Datasource returns correct item ids for a translated entity.'); // Test that the index needs to be updated. - $this->assertEquals(1, $this->index->getTracker()->getIndexedItemsCount(), 'The updated items needs to be reindexed.'); - $this->assertEquals(4, $this->index->getTracker()->getTotalItemsCount(), 'There are four items in total.'); + $this->assertEquals(1, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'The updated items needs to be reindexed.'); + $this->assertEquals(4, $this->index->getTrackerInstance()->getTotalItemsCount(), 'There are four items in total.'); // Delete one translation and test that the datasource returns only three // items. @@ -215,10 +225,10 @@ class LanguageKernelTest extends KernelTestBase { $this->assertEquals($expected, $datasource_item_ids, 'Datasource returns correct item ids for a translated entity.'); // Test reindexing. - $this->assertEquals(3, $this->index->getTracker()->getTotalItemsCount(), 'There are three items in total.'); - $this->assertEquals(1, $this->index->getTracker()->getIndexedItemsCount(), 'The updated items needs to be reindexed.'); + $this->assertEquals(3, $this->index->getTrackerInstance()->getTotalItemsCount(), 'There are three items in total.'); + $this->assertEquals(1, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'The updated items needs to be reindexed.'); $this->index->indexItems(); - $this->assertEquals(3, $this->index->getTracker()->getIndexedItemsCount(), 'Three items are indexed.'); + $this->assertEquals(3, $this->index->getTrackerInstance()->getIndexedItemsCount(), 'Three items are indexed.'); } } diff --git a/tests/src/Kernel/ServerTaskTest.php b/tests/src/Kernel/ServerTaskTest.php index a31595d..8fe8e7a 100644 --- a/tests/src/Kernel/ServerTaskTest.php +++ b/tests/src/Kernel/ServerTaskTest.php @@ -91,8 +91,18 @@ class ServerTaskTest extends KernelTestBase { 'name' => 'Test index', 'id' => 'test_index', 'status' => 1, - 'datasources' => array('entity:user'), - 'tracker' => 'default', + 'datasource_settings' => array( + 'entity:user' => array( + 'plugin_id' => 'entity:user', + 'settings' => array(), + ), + ), + 'tracker_settings' => array( + 'default' => array( + 'plugin_id' => 'default', + 'settings' => array(), + ) + ), 'server' => $this->server->id(), 'options' => array('index_directly' => FALSE), ));