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..b26c388 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 $languageManager; + + /** * {@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; } + + /** + * Retrieves the language manager. + * + * @return \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function getLanguageManager() { + return $this->languageManager ?: \Drupal::languageManager(); + } + + /** + * 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; + } + /** * {@inheritdoc} */ @@ -221,10 +254,24 @@ class ContentEntity extends DatasourcePluginBase { * {@inheritdoc} */ public function loadMultiple(array $ids) { + $allowed_languages = $all_languages = $this->getLanguageManager()->getLanguages(); + + if ($this->isTranslatable()) { + $selected_languages = array_flip($this->configuration['languages']); + if ($this->configuration['default']) { + $allowed_languages = array_diff_key($all_languages, $selected_languages); + } + else { + $allowed_languages = array_intersect_key($all_languages, $selected_languages); + } + } + $entity_ids = array(); foreach ($ids as $item_id) { list($entity_id, $langcode) = explode(':', $item_id, 2); - $entity_ids[$entity_id][$item_id] = $langcode; + if (isset($allowed_languages[$langcode])) { + $entity_ids[$entity_id][$item_id] = $langcode; + } } /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */ @@ -335,21 +382,27 @@ class ContentEntity extends DatasourcePluginBase { * {@inheritdoc} */ public function defaultConfiguration() { + $default_configuration = array(); + if ($this->hasBundles() || $this->isTranslatable()) { + $default_configuration['default'] = 1; + } + if ($this->hasBundles()) { - return array( - 'default' => 1, - 'bundles' => array(), - ); + $default_configuration['bundles'] = array(); } - return array(); + + if ($this->isTranslatable()) { + $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->isTranslatable()) { $form['default'] = array( '#type' => 'radios', '#title' => $this->t('What should be indexed?'), @@ -359,6 +412,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 +426,16 @@ class ContentEntity extends DatasourcePluginBase { ); } + if ($this->isTranslatable()) { + $form['languages'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Languages'), + '#options' => $this->getTranslationOptions(), + '#default_value' => array_combine($this->configuration['languages'], $this->configuration['languages']), + '#multiple' => TRUE, + ); + } + return $form; } @@ -390,6 +457,30 @@ 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 submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $languages = $form_state->getValue('languages', array()); + $languages = array_keys(array_filter($languages)); + $form_state->setValue('languages', $languages); + } + + /** * {@inheritdoc} */ public function getItemId(ComplexDataInterface $item) { @@ -442,15 +533,32 @@ 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))); + if ($this->configuration['default']) { + $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->isTranslatable()) { + if ($summary) { + $summary .= '; '; + } + $languages = array_intersect_key($this->getTranslationOptions(), array_flip($this->configuration['languages'])); + if ($this->configuration['default']) { + $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 +581,16 @@ class ContentEntity extends DatasourcePluginBase { } /** + * Determines whether the entity type supports translations. + * + * @return bool + * TRUE if the entity is translatable, FALSE otherwise. + */ + protected function isTranslatable() { + return $this->getEntityType()->isTranslatable(); + } + + /** * Retrieves all bundles of this datasource's entity type. * * @return array @@ -528,6 +646,7 @@ class ContentEntity extends DatasourcePluginBase { $item_ids[] = "$entity_id:$langcode"; } } + return $item_ids; } diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php index 9c1b69a..530a52b 100644 --- a/src/Tests/IntegrationTest.php +++ b/src/Tests/IntegrationTest.php @@ -658,6 +658,9 @@ class IntegrationTest extends WebTestBase { // Disable "read only" and verify indexing now works again. $edit = array( 'read_only' => FALSE, + 'datasource_configs[entity:node][default]' => TRUE, + 'datasource_configs[entity:node][bundles][article]' => FALSE, + 'datasource_configs[entity:node][bundles][page]' => FALSE, ); $this->drupalPostForm($settings_path, $edit, $this->t('Save')); diff --git a/src/Tests/LanguageIntegrationTest.php b/src/Tests/LanguageIntegrationTest.php new file mode 100644 index 0000000..94a637e --- /dev/null +++ b/src/Tests/LanguageIntegrationTest.php @@ -0,0 +1,138 @@ +drupalLogin($this->adminUser); + + // Add extra languages. + ConfigurableLanguage::createFromLangcode('nl')->save(); + ConfigurableLanguage::createFromLangcode('xx-lolspeak')->save(); + + // Create a test server and index. + $this->getTestServer(); + $index = $this->getTestIndex(); + $this->indexId = $index->id(); + + // 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. + $translation = array('title' => 'test NL', 'body' => 'NL body'); + $article1->addTranslation('nl', $translation)->save(); + $translation = array('title' => 'test2 NL', 'body' => 'NL body2'); + $article2->addTranslation('nl', $translation)->save(); + $translation = array('title' => 'cats', 'body' => 'Cats test'); + $article1->addTranslation('xx-lolspeak', $translation)->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.'); + + // Clear index. + $this->drupalGet($this->getIndexPath()); + $this->drupalPostForm(NULL, array(), $this->t('Clear all indexed data')); + $this->drupalPostForm(NULL, array(), $this->t('Confirm')); + + // Make sure all 5 items are successfully indexed. + $this->drupalGet($this->getIndexPath()); + $this->drupalPostForm(NULL, array(), $this->t('Index now')); + $this->assertText($this->t('Successfully indexed 5 items')); + + // 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; + } + +}