From f1aa6b3e1cfeb40e8246fa02290b1829cc9e2292 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Wed, 4 Oct 2017 15:59:17 +0100 Subject: [PATCH] Issue #2810097 by Lendude, larowlan, jp.stacey, Meenakshi Gupta, rachel_norfolk, dawehner, alexpott: Allow views to provide the canonical entity URL of all entities, not just nodes --- core/modules/node/src/NodeViewsData.php | 8 - .../node/src/Plugin/views/field/Path.php | 4 + .../src/Functional/Views/PathPluginTest.php | 8 +- .../config/schema/views.field.schema.yml | 6 + .../src/Plugin/views/field/EntityLink.php | 43 +++- .../Update/EntityLinkOutputUrlUpdateTest.php | 41 ++++ .../update/entity-link-output-url.php | 19 ++ .../views.view.node_link_update_test.yml | 226 ++++++++++++++++++ .../views.view.test_entity_test_link.yml | 106 ++++++++ .../Kernel/Handler/FieldEntityLinkTest.php | 36 ++- core/modules/views/views.module | 38 +++ core/modules/views/views.post_update.php | 43 ++++ 12 files changed, 564 insertions(+), 14 deletions(-) create mode 100644 core/modules/views/src/Tests/Update/EntityLinkOutputUrlUpdateTest.php create mode 100644 core/modules/views/tests/fixtures/update/entity-link-output-url.php create mode 100644 core/modules/views/tests/fixtures/update/views.view.node_link_update_test.yml diff --git a/core/modules/node/src/NodeViewsData.php b/core/modules/node/src/NodeViewsData.php index b5aebf48fa22..a122203d6f8f 100644 --- a/core/modules/node/src/NodeViewsData.php +++ b/core/modules/node/src/NodeViewsData.php @@ -58,14 +58,6 @@ public function getViewsData() { $data['node_field_data']['sticky']['filter']['type'] = 'yes-no'; $data['node_field_data']['sticky']['sort']['help'] = $this->t('Whether or not the content is sticky. To list sticky content first, set this to descending.'); - $data['node']['path'] = [ - 'field' => [ - 'title' => $this->t('Path'), - 'help' => $this->t('The aliased path to this content.'), - 'id' => 'node_path', - ], - ]; - $data['node']['node_bulk_form'] = [ 'title' => $this->t('Node operations bulk form'), 'help' => $this->t('Add a form element that lets you run operations on multiple nodes.'), diff --git a/core/modules/node/src/Plugin/views/field/Path.php b/core/modules/node/src/Plugin/views/field/Path.php index c1c74ab658a7..5e55a4aa78b0 100644 --- a/core/modules/node/src/Plugin/views/field/Path.php +++ b/core/modules/node/src/Plugin/views/field/Path.php @@ -1,6 +1,7 @@ <?php namespace Drupal\node\Plugin\views\field; +@trigger_error('Drupal\node\Plugin\views\field\Path is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Use @ViewsField("entity_link") with \'output_url_as_text\' set.', E_USER_DEPRECATED); use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\field\FieldPluginBase; @@ -14,6 +15,9 @@ * @ingroup views_field_handlers * * @ViewsField("node_path") + * + * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. + * Use @ViewsField("entity_link") with 'output_url_as_text' set. */ class Path extends FieldPluginBase { diff --git a/core/modules/node/tests/src/Functional/Views/PathPluginTest.php b/core/modules/node/tests/src/Functional/Views/PathPluginTest.php index 3cb4e372c9c0..d8d088d978ac 100644 --- a/core/modules/node/tests/src/Functional/Views/PathPluginTest.php +++ b/core/modules/node/tests/src/Functional/Views/PathPluginTest.php @@ -58,12 +58,18 @@ protected function setUp($import_test_views = TRUE) { } /** - * Tests the node path plugin. + * Tests the node path plugin functionality when converted to entity link. */ public function testPathPlugin() { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); $view = Views::getView('test_node_path_plugin'); + + // The configured deprecated node path plugin should be converted to the + // entity link plugin. + $field = $view->getHandler('page_1', 'field', 'path'); + $this->assertEqual('entity_link', $field['plugin_id']); + $view->initDisplay(); $view->setDisplay('page_1'); $view->initStyle(); diff --git a/core/modules/views/config/schema/views.field.schema.yml b/core/modules/views/config/schema/views.field.schema.yml index 562e8ca4d14c..48b6ef8907e5 100644 --- a/core/modules/views/config/schema/views.field.schema.yml +++ b/core/modules/views/config/schema/views.field.schema.yml @@ -191,6 +191,12 @@ views.field.entity_link: text: type: label label: 'Text to display' + output_url_as_text: + type: boolean + label: 'Output the URL as text' + absolute: + type: boolean + label: 'Output an absolute link' views.field.entity_link_delete: type: views.field.entity_link diff --git a/core/modules/views/src/Plugin/views/field/EntityLink.php b/core/modules/views/src/Plugin/views/field/EntityLink.php index 2cd402cd0070..75b8da394949 100644 --- a/core/modules/views/src/Plugin/views/field/EntityLink.php +++ b/core/modules/views/src/Plugin/views/field/EntityLink.php @@ -2,6 +2,7 @@ namespace Drupal\views\Plugin\views\field; +use Drupal\Core\Form\FormStateInterface; use Drupal\views\ResultRow; /** @@ -20,12 +21,22 @@ public function render(ResultRow $row) { return $this->getEntity($row) ? parent::render($row) : []; } + /** + * {@inheritdoc} + */ + protected function renderLink(ResultRow $row) { + if ($this->options['output_url_as_text']) { + return $this->getUrlInfo($row)->toString(); + } + return parent::renderLink($row); + } + /** * {@inheritdoc} */ protected function getUrlInfo(ResultRow $row) { $template = $this->getEntityLinkTemplate(); - return $this->getEntity($row)->urlInfo($template); + return $this->getEntity($row)->toUrl($template)->setAbsolute($this->options['absolute']); } /** @@ -45,4 +56,34 @@ protected function getDefaultLabel() { return $this->t('view'); } + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['output_url_as_text'] = ['default' => FALSE]; + $options['absolute'] = ['default' => FALSE]; + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + $form['output_url_as_text'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Output the URL as text'), + '#default_value' => $this->options['output_url_as_text'], + ]; + $form['absolute'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Use absolute link (begins with "http://")'), + '#default_value' => $this->options['absolute'], + '#description' => $this->t('Enable this option to output an absolute link. Required if you want to use the path as a link destination.'), + ]; + parent::buildOptionsForm($form, $form_state); + // Only show the 'text' field if we don't want to output the raw URL. + $form['text']['#states']['visible'][':input[name="options[output_url_as_text]"]'] = ['checked' => FALSE]; + } + } diff --git a/core/modules/views/src/Tests/Update/EntityLinkOutputUrlUpdateTest.php b/core/modules/views/src/Tests/Update/EntityLinkOutputUrlUpdateTest.php new file mode 100644 index 000000000000..1e990926fed2 --- /dev/null +++ b/core/modules/views/src/Tests/Update/EntityLinkOutputUrlUpdateTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\views\Tests\Update; + +use Drupal\system\Tests\Update\UpdatePathTestBase; +use Drupal\views\Entity\View; + +/** + * Tests that the additional settings are added to the entity link field. + * + * @see views_post_update_entity_link_url() + * + * @group Update + */ +class EntityLinkOutputUrlUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../tests/fixtures/update/entity-link-output-url.php', + ]; + } + + /** + * Tests that the additional settings are added to the config. + */ + public function testViewsPostUpdateEntityLinkUrl() { + $this->runUpdates(); + + // Load and initialize our test view. + $view = View::load('node_link_update_test'); + $data = $view->toArray(); + // Check that the field contains the new values. + $this->assertIdentical(FALSE, $data['display']['default']['display_options']['fields']['view_node']['output_url_as_text']); + $this->assertIdentical(FALSE, $data['display']['default']['display_options']['fields']['view_node']['absolute']); + } + +} diff --git a/core/modules/views/tests/fixtures/update/entity-link-output-url.php b/core/modules/views/tests/fixtures/update/entity-link-output-url.php new file mode 100644 index 000000000000..8d2aabeed421 --- /dev/null +++ b/core/modules/views/tests/fixtures/update/entity-link-output-url.php @@ -0,0 +1,19 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; +use Drupal\Core\Serialization\Yaml; + +$connection = Database::getConnection(); + +$connection->insert('config') + ->fields([ + 'collection' => '', + 'name' => 'views.view.node_link_update_test', + 'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/fixtures/update/views.view.node_link_update_test.yml'))), + ]) + ->execute(); diff --git a/core/modules/views/tests/fixtures/update/views.view.node_link_update_test.yml b/core/modules/views/tests/fixtures/update/views.view.node_link_update_test.yml new file mode 100644 index 000000000000..ae6eaa86c6b7 --- /dev/null +++ b/core/modules/views/tests/fixtures/update/views.view.node_link_update_test.yml @@ -0,0 +1,226 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: node_link_update_test +label: 'node link update test' +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: 10 + 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 + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + path: + id: path + table: node + field: path + entity_type: node + plugin_id: node_path + 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 + entity_type: node + plugin_id: entity_link + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + 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 + 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: { } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_test_link.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_test_link.yml index ca151de45bc3..d549ed0a2398 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_test_link.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_test_link.yml @@ -296,6 +296,112 @@ display: text: 'Delete entity test' entity_type: entity_test plugin_id: entity_link_delete + canonical_entity_test: + id: canonical_entity_test + table: entity_test + field: view_entity_test + 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: '' + output_url_as_text: true + absolute: false + entity_type: entity_test + plugin_id: entity_link + absolute_entity_test: + id: absolute_entity_test + table: entity_test + field: view_entity_test + 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: '' + output_url_as_text: true + absolute: true + entity_type: entity_test + plugin_id: entity_link filters: { } sorts: id: diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldEntityLinkTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldEntityLinkTest.php index 874f03d4fccf..a7f5116f35ac 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FieldEntityLinkTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FieldEntityLinkTest.php @@ -67,11 +67,11 @@ protected function setUpFixtures() { */ public function testEntityLink() { // Anonymous users cannot see edit/delete links. - $expected_results = ['canonical' => TRUE, 'edit-form' => FALSE, 'delete-form' => FALSE]; + $expected_results = ['canonical' => TRUE, 'edit-form' => FALSE, 'delete-form' => FALSE, 'canonical_raw' => TRUE, 'canonical_raw_absolute' => TRUE]; $this->doTestEntityLink(\Drupal::currentUser(), $expected_results); // Admin users cannot see all links. - $expected_results = ['canonical' => TRUE, 'edit-form' => TRUE, 'delete-form' => TRUE]; + $expected_results = ['canonical' => TRUE, 'edit-form' => TRUE, 'delete-form' => TRUE, 'canonical_raw' => TRUE, 'canonical_raw_absolute' => TRUE]; $this->doTestEntityLink($this->adminUser, $expected_results); } @@ -94,16 +94,39 @@ protected function doTestEntityLink(AccountInterface $account, $expected_results 'label' => 'View entity test', 'field_id' => 'view_entity_test', 'destination' => FALSE, + 'link' => TRUE, + 'options' => [], + 'relationship' => 'canonical', ], 'edit-form' => [ 'label' => 'Edit entity test', 'field_id' => 'edit_entity_test', 'destination' => TRUE, + 'link' => TRUE, + 'options' => [], + 'relationship' => 'edit-form', ], 'delete-form' => [ 'label' => 'Delete entity test', 'field_id' => 'delete_entity_test', 'destination' => TRUE, + 'link' => TRUE, + 'options' => [], + 'relationship' => 'delete-form', + ], + 'canonical_raw' => [ + 'field_id' => 'canonical_entity_test', + 'destination' => FALSE, + 'link' => FALSE, + 'options' => [], + 'relationship' => 'canonical', + ], + 'canonical_raw_absolute' => [ + 'field_id' => 'absolute_entity_test', + 'destination' => FALSE, + 'link' => FALSE, + 'options' => ['absolute' => TRUE], + 'relationship' => 'canonical', ], ]; @@ -112,9 +135,14 @@ protected function doTestEntityLink(AccountInterface $account, $expected_results foreach ($expected_results as $template => $expected_result) { $expected_link = ''; if ($expected_result) { - $path = $entity->url($template); + $path = $entity->url($info[$template]['relationship'], $info[$template]['options']); $destination = $info[$template]['destination'] ? '?destination=/' : ''; - $expected_link = '<a href="' . $path . $destination . '" hreflang="en">' . $info[$template]['label'] . '</a>'; + if ($info[$template]['link']) { + $expected_link = '<a href="' . $path . $destination . '" hreflang="en">' . $info[$template]['label'] . '</a>'; + } + else { + $expected_link = $path; + } } $link = $view->style_plugin->getField($index, $info[$template]['field_id']); $this->assertEqual($link, $expected_link); diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 8b707644d343..f484f1833a84 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -13,6 +13,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\views\Plugin\Derivative\ViewsLocalTask; +use Drupal\views\ViewEntityInterface; use Drupal\views\ViewExecutable; use Drupal\views\Entity\View; use Drupal\views\Render\ViewsRenderPipelineMarkup; @@ -861,3 +862,40 @@ function views_view_delete(EntityInterface $entity) { } } } + +/** + * Implements hook_view_presave(). + * + * Provides a BC layer for modules providing old configurations. + */ +function views_view_presave(ViewEntityInterface $view) { + $displays = $view->get('display'); + $changed = FALSE; + foreach ($displays as $display_name => &$display) { + if (isset($display['display_options']['fields'])) { + foreach ($display['display_options']['fields'] as $field_name => &$field) { + if (isset($field['plugin_id']) && $field['plugin_id'] === 'entity_link') { + // Add any missing settings for entity_link. + if (!isset($field['output_url_as_text'])) { + $field['output_url_as_text'] = FALSE; + $changed = TRUE; + } + if (!isset($field['absolute'])) { + $field['absolute'] = FALSE; + $changed = TRUE; + } + } + elseif (isset($field['plugin_id']) && $field['plugin_id'] === 'node_path') { + // Convert the use of node_path to entity_link. + $field['plugin_id'] = 'entity_link'; + $field['field'] = 'view_node'; + $field['output_url_as_text'] = TRUE; + $changed = TRUE; + } + } + } + } + if ($changed) { + $view->set('display', $displays); + } +} diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php index e09159e7e01c..0f84d7b55a7c 100644 --- a/core/modules/views/views.post_update.php +++ b/core/modules/views/views.post_update.php @@ -213,3 +213,46 @@ function views_post_update_revision_metadata_fields() { $view->save(); }); } + +/** + * Add additional settings to the entity link field and convert node_path usage + * to entity_link. + */ +function views_post_update_entity_link_url() { + // Load all views. + $views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple(); + + /* @var \Drupal\views\Entity\View[] $views */ + foreach ($views as $view) { + $displays = $view->get('display'); + $changed = FALSE; + foreach ($displays as $display_name => &$display) { + if (isset($display['display_options']['fields'])) { + foreach ($display['display_options']['fields'] as $field_name => &$field) { + if (isset($field['plugin_id']) && $field['plugin_id'] === 'entity_link') { + // Add any missing settings for entity_link. + if (!isset($field['output_url_as_text'])) { + $field['output_url_as_text'] = FALSE; + $changed = TRUE; + } + if (!isset($field['absolute'])) { + $field['absolute'] = FALSE; + $changed = TRUE; + } + } + elseif (isset($field['plugin_id']) && $field['plugin_id'] === 'node_path') { + // Convert the use of node_path to entity_link. + $field['plugin_id'] = 'entity_link'; + $field['field'] = 'view_node'; + $field['output_url_as_text'] = TRUE; + $changed = TRUE; + } + } + } + } + if ($changed) { + $view->set('display', $displays); + $view->save(); + } + } +} -- GitLab