diff --git a/core/modules/views/src/Plugin/views/field/EntityLink.php b/core/modules/views/src/Plugin/views/field/EntityLink.php index 75b8da39494..f7b2877a6c6 100644 --- a/core/modules/views/src/Plugin/views/field/EntityLink.php +++ b/core/modules/views/src/Plugin/views/field/EntityLink.php @@ -36,7 +36,11 @@ protected function renderLink(ResultRow $row) { */ protected function getUrlInfo(ResultRow $row) { $template = $this->getEntityLinkTemplate(); - return $this->getEntity($row)->toUrl($template)->setAbsolute($this->options['absolute']); + $entity = $this->getEntity($row); + if ($this->languageManager->isMultilingual()) { + $entity = $this->getEntityTranslation($entity, $row); + } + return $entity->toUrl($template)->setAbsolute($this->options['absolute']); } /** diff --git a/core/modules/views/src/Plugin/views/field/LinkBase.php b/core/modules/views/src/Plugin/views/field/LinkBase.php index 31dca4c7c3c..06486a4fb0f 100644 --- a/core/modules/views/src/Plugin/views/field/LinkBase.php +++ b/core/modules/views/src/Plugin/views/field/LinkBase.php @@ -3,9 +3,13 @@ namespace Drupal\views\Plugin\views\field; use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Routing\RedirectDestinationTrait; +use Drupal\views\Entity\Render\EntityTranslationRenderTrait; use Drupal\views\ResultRow; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -17,6 +21,7 @@ abstract class LinkBase extends FieldPluginBase { use RedirectDestinationTrait; + use EntityTranslationRenderTrait; /** * The access manager service. @@ -32,6 +37,27 @@ */ protected $currentUser; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity repository. + * + * @var \Drupal\Core\Entity\EntityRepositoryInterface + */ + protected $entityRepository; + /** * Constructs a LinkBase object. * @@ -43,10 +69,32 @@ * The plugin implementation definition. * @param \Drupal\Core\Access\AccessManagerInterface $access_manager * The access manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface|null $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Entity\EntityRepositoryInterface|null $entity_repository + * The entity repository. + * @param \Drupal\Core\Language\LanguageManagerInterface|null $language_manager + * The language manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, AccessManagerInterface $access_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, AccessManagerInterface $access_manager, EntityTypeManagerInterface $entity_type_manager = NULL, EntityRepositoryInterface $entity_repository = NULL, LanguageManagerInterface $language_manager = NULL) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->accessManager = $access_manager; + if (!$entity_type_manager) { + @trigger_error('Passing the entity type manager service to \Drupal\views\Plugin\views\field\LinkBase::__construct is required since 8.7.0, see https://www.drupal.org/node/3023427.', E_USER_DEPRECATED); + $entity_type_manager = \Drupal::service('entity_type.manager'); + } + $this->entityTypeManager = $entity_type_manager; + if (!$entity_repository) { + @trigger_error('Passing the entity repository service to \Drupal\views\Plugin\views\field\LinkBase::__construct is required since 8.7.0, see https://www.drupal.org/node/3023427.', E_USER_DEPRECATED); + $entity_repository = \Drupal::service('entity.repository'); + } + $this->entityRepository = $entity_repository; + + if (!$language_manager) { + @trigger_error('Passing the language manager service to \Drupal\views\Plugin\views\field\LinkBase::__construct is required since 8.7.0, see https://www.drupal.org/node/3023427.', E_USER_DEPRECATED); + $language_manager = \Drupal::service('language_manager'); + } + $this->languageManager = $language_manager; } /** @@ -57,7 +105,10 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('access_manager') + $container->get('access_manager'), + $container->get('entity_type.manager'), + $container->get('entity.repository'), + $container->get('language_manager') ); } @@ -115,6 +166,9 @@ public function usesGroupBy() { * {@inheritdoc} */ public function query() { + if ($this->languageManager->isMultilingual()) { + $this->getEntityTranslationRenderer()->query($this->query, $this->relationship); + } $this->addAdditionalFields(); } @@ -178,9 +232,8 @@ protected function renderLink(ResultRow $row) { */ protected function addLangcode(ResultRow $row) { $entity = $this->getEntity($row); - $langcode_key = $entity ? $entity->getEntityType()->getKey('langcode') : FALSE; - if ($langcode_key && isset($this->aliases[$langcode_key])) { - $this->options['alter']['language'] = $entity->language(); + if ($this->languageManager->isMultilingual()) { + $this->options['alter']['language'] = $this->getEntityTranslation($entity, $row)->language(); } } @@ -194,4 +247,39 @@ protected function getDefaultLabel() { return $this->t('link'); } + /** + * {@inheritdoc} + */ + public function getEntityTypeId() { + return $this->getEntityType(); + } + + /** + * {@inheritdoc} + */ + protected function getEntityTypeManager() { + return $this->entityTypeManager; + } + + /** + * {@inheritdoc} + */ + protected function getEntityRepository() { + return $this->entityRepository; + } + + /** + * {@inheritdoc} + */ + protected function getLanguageManager() { + return $this->languageManager; + } + + /** + * {@inheritdoc} + */ + protected function getView() { + return $this->view; + } + } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_link_base_links.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_link_base_links.yml new file mode 100644 index 00000000000..7031a82ee25 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_link_base_links.yml @@ -0,0 +1,337 @@ +langcode: en +status: true +dependencies: + module: + - content_translation + - node + - user +id: test_link_base_links +label: 'test link base links' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 25 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + row: + type: fields + options: + default_field_elements: true + inline: { } + separator: '' + hide_empty: false + fields: + view_node: + id: view_node + table: node + field: view_node + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: false + absolute: false + entity_type: node + plugin_id: entity_link + delete_node: + id: delete_node + table: node + field: delete_node + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: delete + output_url_as_text: false + absolute: false + entity_type: node + plugin_id: entity_link_delete + edit_node: + id: edit_node + table: node + field: edit_node + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: edit + output_url_as_text: false + absolute: false + entity_type: node + plugin_id: entity_link_edit + view_node_1: + id: view_node_1 + table: node + field: view_node + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: true + absolute: false + entity_type: node + plugin_id: entity_link + filters: { } + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + entity_type: node + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + title: 'test link base links' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test-link-base-links + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/views/tests/src/Functional/Handler/FieldEntityLinkBaseTest.php b/core/modules/views/tests/src/Functional/Handler/FieldEntityLinkBaseTest.php new file mode 100644 index 00000000000..bd3f3f65cf7 --- /dev/null +++ b/core/modules/views/tests/src/Functional/Handler/FieldEntityLinkBaseTest.php @@ -0,0 +1,91 @@ +drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + + // Add languages and refresh the container so the entity manager will have + // fresh data. + ConfigurableLanguage::createFromLangcode('hu')->save(); + ConfigurableLanguage::createFromLangcode('es')->save(); + $this->rebuildContainer(); + + // Create an English and Hungarian nodes and add a Spanish translation. + foreach (['en', 'hu'] as $langcode) { + $entity = Node::create([ + 'title' => $this->randomMachineName(), + 'type' => 'article', + 'langcode' => $langcode, + ]); + $entity->save(); + $translation = $entity->addTranslation('es'); + $translation->set('title', $entity->getTitle() . ' in Spanish'); + $translation->save(); + } + + $this->drupalLogin($this->rootUser); + + } + + /** + * Tests entity link fields. + */ + public function testEntityLink() { + $this->drupalGet('test-link-base-links'); + $session = $this->assertSession(); + + // Tests 'Link to Content'. + $session->linkByHrefExists('/node/1'); + $session->linkByHrefExists('/es/node/1'); + $session->linkByHrefExists('/hu/node/2'); + $session->linkByHrefExists('/es/node/2'); + + // Tests 'Link to delete Content'. + $session->linkByHrefExists('/node/1/delete'); + $session->linkByHrefExists('/es/node/1/delete'); + $session->linkByHrefExists('/hu/node/2/delete'); + $session->linkByHrefExists('/es/node/2/delete'); + + // Tests 'Link to edit Content'. + $session->linkByHrefExists('/node/1/edit'); + $session->linkByHrefExists('/es/node/1/edit'); + $session->linkByHrefExists('/hu/node/2/edit'); + $session->linkByHrefExists('/es/node/2/edit'); + + // Tests the second 'Link to Content' rendered as text. + $session->pageTextContains('/node/1'); + $session->pageTextContains('/es/node/1'); + $session->pageTextContains('/hu/node/2'); + $session->pageTextContains('/es/node/2'); + } + +}