diff --git a/config/schema/linkit.schema.yml b/config/schema/linkit.schema.yml index c6e3f1d..63511ee 100644 --- a/config/schema/linkit.schema.yml +++ b/config/schema/linkit.schema.yml @@ -47,6 +47,10 @@ linkit.matcher.entity: type: string limit: type: integer + translated_entities: + type: string + hide_untranslated_entities: + type: boolean linkit.matcher.entity:*: type: linkit.matcher.entity diff --git a/js/linkit.autocomplete.js b/js/linkit.autocomplete.js index 558278d..0d86429 100644 --- a/js/linkit.autocomplete.js +++ b/js/linkit.autocomplete.js @@ -3,7 +3,7 @@ * Linkit Autocomplete based on jQuery UI. */ -(function ($, Drupal, _) { +(function ($, Drupal, _, drupalSettings) { 'use strict'; @@ -47,6 +47,11 @@ success: sourceCallbackHandler, data: {q: term} }, autocomplete.ajax); + + if (drupalSettings.path.hasOwnProperty('currentQuery') && drupalSettings.path.currentQuery.hasOwnProperty('language_content_entity')) { + options.data.language_content_entity = drupalSettings.path.currentQuery.language_content_entity; + } + $.ajax(this.element.attr('data-autocomplete-path'), options); } } @@ -212,4 +217,4 @@ } }; -})(jQuery, Drupal, _); +})(jQuery, Drupal, _, drupalSettings); diff --git a/src/Plugin/Linkit/Matcher/EntityMatcher.php b/src/Plugin/Linkit/Matcher/EntityMatcher.php index 787c409..74dac54 100644 --- a/src/Plugin/Linkit/Matcher/EntityMatcher.php +++ b/src/Plugin/Linkit/Matcher/EntityMatcher.php @@ -9,9 +9,12 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\TranslatableInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\linkit\ConfigurableMatcherBase; @@ -90,6 +93,13 @@ class EntityMatcher extends ConfigurableMatcherBase { */ protected $targetType; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + /** * The substitution manager. * @@ -100,7 +110,7 @@ class EntityMatcher extends ConfigurableMatcherBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user, SubstitutionManagerInterface $substitution_manager, LanguageManagerInterface $language_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); if (empty($plugin_definition['target_entity'])) { @@ -114,6 +124,7 @@ class EntityMatcher extends ConfigurableMatcherBase { $this->currentUser = $current_user; $this->targetType = $plugin_definition['target_entity']; $this->substitutionManager = $substitution_manager; + $this->languageManager = $language_manager; } /** @@ -130,7 +141,8 @@ class EntityMatcher extends ConfigurableMatcherBase { $container->get('entity.repository'), $container->get('module_handler'), $container->get('current_user'), - $container->get('plugin.manager.linkit.substitution') + $container->get('plugin.manager.linkit.substitution'), + $container->get('language_manager') ); } @@ -174,6 +186,26 @@ class EntityMatcher extends ConfigurableMatcherBase { } } + $translated_entities_option = $this->t('No'); + if ($this->configuration['translated_entities'] == 1) { + $translated_entities_option = $this->t('Current UI language'); + } + if ($this->configuration['translated_entities'] == 2) { + $translated_entities_option = $this->t('Current content language'); + } + if ($this->configuration['translated_entities'] == 3) { + $translated_entities_option = $this->t('Actual match'); + } + $summery[] = $this->t('Use translated entities: @translated_entities', [ + '@translated_entities' => $translated_entities_option, + ]); + + if (in_array($this->configuration['translated_entities'], [1, 2])) { + $summery[] = $this->t('Hide untranslated entities: @hide_untranslated_entities', [ + '@hide_untranslated_entities' => $this->configuration['hide_untranslated_entities'] ? $this->t('Yes') : $this->t('No'), + ]); + } + return $summery; } @@ -185,6 +217,8 @@ class EntityMatcher extends ConfigurableMatcherBase { 'metadata' => '', 'bundles' => [], 'group_by_bundle' => FALSE, + 'translated_entities' => 0, + 'hide_untranslated_entities' => 0, 'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION, 'limit' => static::DEFAULT_LIMIT, ] + parent::defaultConfiguration(); @@ -288,6 +322,40 @@ class EntityMatcher extends ConfigurableMatcherBase { '#description' => $this->t('Limit the amount of results displayed when searching.'), '#default_value' => $this->configuration['limit'], ]; + + $form['translations'] = [ + '#type' => 'details', + '#title' => $this->t('Translations'), + '#open' => TRUE, + ]; + + $form['translations']['translated_entities'] = [ + '#type' => 'select', + '#title' => $this->t('Use translated entities'), + '#description' => $this->t('The translated entities will be used to create suggestions (if possible)'), + '#default_value' => $this->configuration['translated_entities'], + '#options' => [ + 0 => $this->t('No'), + 1 => $this->t('Current UI language'), + 2 => $this->t('Current content language'), + 3 => $this->t('Actual match'), + ], + ]; + + $form['translations']['hide_untranslated_entities'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Hide untranslated entities'), + '#default_value' => $this->configuration['hide_untranslated_entities'], + '#description' => $this->t('The entity will be omitted in suggestions, if it has no translation in current language.'), + '#states' => [ + 'visible' => [ + [':input[name="translated_entities"]' => ['value' => 1]], + 'or', + [':input[name="translated_entities"]' => ['value' => 2]], + ], + ], + ]; + return $form; } @@ -304,6 +372,8 @@ class EntityMatcher extends ConfigurableMatcherBase { $this->configuration['metadata'] = $form_state->getValue('metadata'); $this->configuration['bundles'] = $form_state->getValue('bundles'); $this->configuration['group_by_bundle'] = $form_state->getValue('group_by_bundle'); + $this->configuration['translated_entities'] = $form_state->getValue('translated_entities'); + $this->configuration['hide_untranslated_entities'] = $form_state->getValue('hide_untranslated_entities'); $this->configuration['substitution_type'] = $form_state->getValue('substitution_type'); $this->configuration['limit'] = $form_state->getValue('limit'); } @@ -342,11 +412,50 @@ class EntityMatcher extends ConfigurableMatcherBase { continue; } + if ($this->configuration['translated_entities'] + && $entity instanceof TranslatableInterface + && !in_array($entity->language()->getId(), [ + LanguageInterface::LANGCODE_NOT_APPLICABLE, + LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]) + ) { + if (in_array($this->configuration['translated_entities'], [1, 2])) { + // If we need to show an entity in current content/UI language, just + // try to retrieve it. + $type = $this->configuration['translated_entities'] == 2 + ? LanguageInterface::TYPE_CONTENT + : LanguageInterface::TYPE_INTERFACE; + $langcode = $this->languageManager->getCurrentLanguage($type)->getId(); + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + elseif ($this->configuration['hide_untranslated_entities']) { + continue; + } + } + elseif ($this->configuration['translated_entities'] == 3) { + // If we need to show actually matched entity, we can just retrieve + // them from the entity query results since it returns only ids. + // So we will iterate over all existing translations and looking + // for matched label. + $languages = $entity->getTranslationLanguages(); + foreach ($languages as $language) { + $langcode = $language->getid(); + if ($entity->hasTranslation($langcode)) { + $entity_translation = $entity->getTranslation($langcode); + if (stripos($entity_translation->label(), $string) !== FALSE) { + $entity = $entity_translation; + break; + } + } + } + } + } + $entity = $this->entityRepository->getTranslationFromContext($entity); $suggestion = $this->createSuggestion($entity); $suggestions->addSuggestion($suggestion); } - return $suggestions; } @@ -492,6 +601,10 @@ class EntityMatcher extends ConfigurableMatcherBase { */ protected function buildPath(EntityInterface $entity) { $path = $entity->toUrl('canonical', ['path_processing' => FALSE])->toString(); + $type = $this->configuration['translated_entities'] == 2 + ? LanguageInterface::TYPE_CONTENT + : LanguageInterface::TYPE_INTERFACE; + $default_langcode = $this->languageManager->getDefaultLanguage($type)->getId(); // For media entities, check if standalone URLs are allowed. If not, then // strip '/edit' from the end of the canonical URL returned // by $entity->toUrl(). @@ -502,6 +615,9 @@ class EntityMatcher extends ConfigurableMatcherBase { $path = substr($path, 0, -5); } } + else if ($entity->getEntityTypeId() == 'node' && $default_langcode != $entity->language()->getId()) { + $path = '/' . $entity->language()->getId() . $path; + } return $path; } @@ -532,3 +648,4 @@ class EntityMatcher extends ConfigurableMatcherBase { } } +