diff --git a/config/schema/search_api.datasource.schema.yml b/config/schema/search_api.datasource.schema.yml index c607af8..18ae3cb 100644 --- a/config/schema/search_api.datasource.schema.yml +++ b/config/schema/search_api.datasource.schema.yml @@ -11,3 +11,9 @@ sequence: type: string label: 'An entity bundle' + languages: + type: sequence + label: 'The selected languages' + sequence: + type: string + label: 'An entity language' diff --git a/src/Plugin/search_api/datasource/ContentEntity.php b/src/Plugin/search_api/datasource/ContentEntity.php index b292c51..1d44c6c 100644 --- a/src/Plugin/search_api/datasource/ContentEntity.php +++ b/src/Plugin/search_api/datasource/ContentEntity.php @@ -14,6 +14,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Render\Element; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\TypedDataManager; @@ -56,6 +57,13 @@ class ContentEntity extends DatasourcePluginBase { protected $configFactory; /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManger; + + /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, array $plugin_definition) { @@ -91,6 +99,10 @@ class ContentEntity extends DatasourcePluginBase { $config_factory = $container->get('config.factory'); $datasource->setConfigFactory($config_factory); + /** @var $language_manager \Drupal\Core\Language\LanguageManagerInterface */ + $language_manager = $container->get('language_manager'); + $datasource->setLanguageManager($language_manager); + return $datasource; } @@ -195,6 +207,27 @@ class ContentEntity extends DatasourcePluginBase { $this->configFactory = $config_factory; return $this; } + + /** + * Sets the language manager. + * + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The new language manager. + */ + public function setLanguageManager(LanguageManagerInterface $language_manager) { + $this->languageManager = $language_manager; + } + + /** + * Retrieves the language manager. + * + * @return \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function getLanguageManager() { + return $this->languageManger ?: \Drupal::getContainer()->get('language_manager'); + } + /** * {@inheritdoc} */ @@ -335,21 +368,27 @@ class ContentEntity extends DatasourcePluginBase { * {@inheritdoc} */ public function defaultConfiguration() { + $default_configuration = array(); + if ($this->hasBundles() || $this->isTranslated()) { + $default_configuration['default'] = 1; + } + if ($this->hasBundles()) { - return array( - 'default' => 1, - 'bundles' => array(), - ); + $default_configuration['bundles'] = array(); } - return array(); + + if ($this->isTranslated()) { + $default_configuration['languages'] = array(); + } + + return $default_configuration; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - if ($this->hasBundles()) { - $bundles = $this->getEntityBundleOptions(); + if ($this->hasBundles() || $this->isTranslated()) { $form['default'] = array( '#type' => 'radios', '#title' => $this->t('What should be indexed?'), @@ -359,6 +398,10 @@ class ContentEntity extends DatasourcePluginBase { ), '#default_value' => $this->configuration['default'], ); + } + + if ($this->hasBundles()) { + $bundles = $this->getEntityBundleOptions(); $form['bundles'] = array( '#type' => 'checkboxes', '#title' => $this->t('Bundles'), @@ -369,6 +412,16 @@ class ContentEntity extends DatasourcePluginBase { ); } + if ($this->isTranslated()) { + $form['languages'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Languages'), + '#options' => $this->getTranslationOptions(), + '#default_value' => $this->configuration['languages'], + '#multiple' => TRUE, + ); + } + return $form; } @@ -390,6 +443,21 @@ class ContentEntity extends DatasourcePluginBase { } /** + * Retrieves the available languages of this entity type as an options list. + * + * @return array + * An associative array of language labels, keyed by the language name. + */ + protected function getTranslationOptions() { + $options = array(); + foreach ($this->getLanguageManager()->getLanguages() as $language) { + $options[$language->getId()] = $language->getName(); + } + + return $options; + } + + /** * {@inheritdoc} */ public function getItemId(ComplexDataInterface $item) { @@ -442,15 +510,29 @@ class ContentEntity extends DatasourcePluginBase { */ public function getDescription() { $summary = ''; + + // Add bundle information in the description. if ($this->hasBundles()) { $bundles = array_values(array_intersect_key($this->getEntityBundleOptions(), array_filter($this->configuration['bundles']))); if ($this->configuration['default'] == TRUE) { - $summary = $this->t('Excluded bundles: @bundles', array('@bundles' => implode(', ', $bundles))); + $summary .= $this->t('Excluded bundles: @bundles', array('@bundles' => implode(', ', $bundles))); + } + else { + $summary .= $this->t('Included bundles: @bundles', array('@bundles' => implode(', ', $bundles))); + } + } + + // Add language information in the description. + if ($this->isTranslated()) { + $languages = array_values(array_intersect_key($this->getLanguageManager()->getLanguages(), array_filter($this->configuration['languages']))); + if ($this->configuration['default'] == TRUE) { + $summary .= $this->t('Excluded languages: @languages', array('@languages' => implode(', ', $languages))); } else { - $summary = $this->t('Included bundles: @bundles', array('@bundles' => implode(', ', $bundles))); + $summary .= $this->t('Included languages: @languages', array('@languages' => implode(', ', $languages))); } } + return $summary; } @@ -473,6 +555,16 @@ class ContentEntity extends DatasourcePluginBase { } /** + * Determines whether the entity type supports translations. + * + * @return bool + * TRUE if the entity is translatable, FALSE otherwise. + */ + protected function isTranslated() { + return $this->getEntityType()->isTranslatable(); + } + + /** * Retrieves all bundles of this datasource's entity type. * * @return array @@ -519,15 +611,34 @@ class ContentEntity extends DatasourcePluginBase { return NULL; } + $allowed_languages = array(); + $all_languages = $this->getLanguageManager()->getLanguages(); + $configuration = $this->configuration; + + if ($configuration['default'] == TRUE) { + $allowed_languages = $all_languages; + foreach ($configuration['languages'] as $langcode) { + unset($allowed_languages[$langcode]); + } + } + else { + foreach ($configuration['languages'] as $langcode => $checked) { + $allowed_languages[$langcode] = $all_languages[$langcode]; + } + } + // For all the loaded entities, compute all their item IDs (one for each // translation). $item_ids = array(); /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ foreach ($this->getEntityStorage()->loadMultiple($entity_ids) as $entity_id => $entity) { foreach (array_keys($entity->getTranslationLanguages()) as $langcode) { - $item_ids[] = "$entity_id:$langcode"; + if (array_key_exists($langcode, $allowed_languages)) { + $item_ids[] = "$entity_id:$langcode"; + } } } + return $item_ids; } diff --git a/src/Tests/IndexIntegrationTest.php b/src/Tests/IndexIntegrationTest.php new file mode 100644 index 0000000..a6478b9 --- /dev/null +++ b/src/Tests/IndexIntegrationTest.php @@ -0,0 +1,148 @@ +drupalLogin($this->adminUser); + + // Add extra languages. + ConfigurableLanguage::createFromLangcode('nl')->save(); + ConfigurableLanguage::createFromLangcode('xx-lolspeak')->save(); + + // Create a server. + $server = $this->getTestServer(); + $this->serverId = $server->id(); + $this->indexId = 'test_index'; + + // Create an index with default settings. + $this->drupalGet('admin/config/search/search-api/add-index'); + $index_settings = array( + 'name' => 'Search API test index', + 'id' => $this->indexId, + 'status' => 1, + 'description' => 'An index used for testing.', + 'server' => $this->serverId, + 'datasources[]' => array('entity:node'), + ); + $this->drupalPostForm(NULL, $index_settings, $this->t('Save')); + $this->assertText($this->t('The index was successfully saved.')); + + // Create 2 articles. + $article1 = $this->drupalCreateNode(array('type' => 'article')); + $article2 = $this->drupalCreateNode(array('type' => 'article')); + + // Those 2 new nodes should be added to the tracking table immediately. + $tracked_items = $this->countTrackedItems(); + $this->assertEqual($tracked_items, 2, 'Two items are tracked.'); + + // Add translations. + $article1->addTranslation('nl', array('title' => 'test NL', 'body' => 'NL body')) + ->save(); + $article2->addTranslation('nl', array('title' => 'test2 NL', 'body' => 'NL body2')) + ->save(); + $article1->addTranslation('xx-lolspeak', array('title' => 'cats', 'body' => 'Cats test')) + ->save(); + + // The translations should be tracked as well, so we have a total of 5 + // indexed items. + $tracked_items = $this->countTrackedItems(); + $this->assertEqual($tracked_items, 5, 'Five items are tracked.'); + + // Change the datasource to disallow indexing of dutch. + $form_values = array('datasource_configs[entity:node][languages][nl]' => 1); + $this->drupalGet($this->getIndexPath('edit')); + $this->drupalPostForm(NULL, $form_values, $this->t('Save')); + $this->assertResponse(200); + $this->assertText('The index was successfully saved.'); + + // Make sure that we only have 3 indexed items now. The 2 original nodes + // + 1 translation in lolspeak, the 2 dutch translations should be ignored. + $this->drupalGet($this->getIndexPath()); + $this->drupalPostForm(NULL, array(), $this->t('Index now')); + $this->assertText($this->t('Successfully indexed 3 items')); + + // Change the datasource to only allow indexing of dutch. + $form_values = array( + 'datasource_configs[entity:node][default]' => 0, + 'datasource_configs[entity:node][bundles][article]' => 1, + 'datasource_configs[entity:node][languages][nl]' => 1, + ); + $this->drupalGet($this->getIndexPath('edit')); + $this->drupalPostForm(NULL, $form_values, $this->t('Save')); + $this->assertResponse(200); + $this->assertText('The index was successfully saved.'); + + // Make sure that we only have 2 index items. The only indexed items should + // be the dutch translations. + $this->drupalGet($this->getIndexPath()); + $this->drupalPostForm(NULL, array(), $this->t('Index now')); + $this->assertText($this->t('Successfully indexed 2 items')); + } + + /** + * Counts the number of tracked items in the test index. + * + * @return int + * The number of tracked items in the test index. + */ + protected function countTrackedItems() { + /** @var \Drupal\search_api\IndexInterface $index */ + $index = Index::load($this->indexId); + return $index->getTracker()->getTotalItemsCount(); + } + + /** + * Returns the system path for the test index. + * + * @param string|null $tab + * (optional) If set, the path suffix for a specific index tab. + * + * @return string + * A system path. + */ + protected function getIndexPath($tab = NULL) { + $path = 'admin/config/search/search-api/index/' . $this->indexId; + if ($tab) { + $path .= "/$tab"; + } + return $path; + } + +}