From 30c696045997aa75769bcbc39e9023cbc99f771d Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Fri, 30 Oct 2015 13:56:47 +0000 Subject: [PATCH] Issue #2500931 by mikeker, jhedstrom, Berdir: Views feed doesn't encode embedded HTML anymore --- .../aggregator/src/Plugin/views/row/Rss.php | 1 - .../comment/src/Plugin/views/row/Rss.php | 8 +- .../modules/node/src/Plugin/views/row/Rss.php | 8 +- .../views/src/Plugin/views/row/RssFields.php | 2 + .../src/Tests/Plugin/DisplayFeedTest.php | 20 +++- .../views.view.test_display_feed.yml | 112 +++++++++++++++++- core/modules/views/views.theme.inc | 13 +- 7 files changed, 140 insertions(+), 24 deletions(-) diff --git a/core/modules/aggregator/src/Plugin/views/row/Rss.php b/core/modules/aggregator/src/Plugin/views/row/Rss.php index b34bb8feead8..3dc20ebac3b1 100644 --- a/core/modules/aggregator/src/Plugin/views/row/Rss.php +++ b/core/modules/aggregator/src/Plugin/views/row/Rss.php @@ -50,7 +50,6 @@ class Rss extends RssPluginBase { $item = new \stdClass(); foreach ($entity as $name => $field) { - // views_view_row_rss takes care about the escaping. $item->{$name} = $field->value; } diff --git a/core/modules/comment/src/Plugin/views/row/Rss.php b/core/modules/comment/src/Plugin/views/row/Rss.php index 85eacaa38c6d..f4e62c72ec17 100644 --- a/core/modules/comment/src/Plugin/views/row/Rss.php +++ b/core/modules/comment/src/Plugin/views/row/Rss.php @@ -84,8 +84,6 @@ class Rss extends RssPluginBase { return; } - $description_build = []; - $comment->link = $comment->url('canonical', array('absolute' => TRUE)); $comment->rss_namespaces = array(); $comment->rss_elements = array( @@ -113,13 +111,11 @@ class Rss extends RssPluginBase { $this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $comment->rss_namespaces); } + $item = new \stdClass(); if ($view_mode != 'title') { // We render comment contents. - $description_build = $build; + $item->description = $build; } - - $item = new \stdClass(); - $item->description = $description_build; $item->title = $comment->label(); $item->link = $comment->link; // Provide a reference so that the render call in diff --git a/core/modules/node/src/Plugin/views/row/Rss.php b/core/modules/node/src/Plugin/views/row/Rss.php index ae2b8712d51f..3f2deeef2711 100644 --- a/core/modules/node/src/Plugin/views/row/Rss.php +++ b/core/modules/node/src/Plugin/views/row/Rss.php @@ -108,8 +108,6 @@ class Rss extends RssPluginBase { return; } - $description_build = []; - $node->link = $node->url('canonical', array('absolute' => TRUE)); $node->rss_namespaces = array(); $node->rss_elements = array( @@ -149,13 +147,11 @@ class Rss extends RssPluginBase { $this->view->style_plugin->namespaces += $xml_rdf_namespaces; } + $item = new \stdClass(); if ($display_mode != 'title') { // We render node contents. - $description_build = $build; + $item->description = $build; } - - $item = new \stdClass(); - $item->description = $description_build; $item->title = $node->label(); $item->link = $node->link; // Provide a reference so that the render call in diff --git a/core/modules/views/src/Plugin/views/row/RssFields.php b/core/modules/views/src/Plugin/views/row/RssFields.php index 25a75d1da003..5fa91cc672a0 100644 --- a/core/modules/views/src/Plugin/views/row/RssFields.php +++ b/core/modules/views/src/Plugin/views/row/RssFields.php @@ -147,8 +147,10 @@ class RssFields extends RowPluginBase { // @todo Views should expect and store a leading /. See: // https://www.drupal.org/node/2423913 $item->link = Url::fromUserInput('/' . $this->getField($row_index, $this->options['link_field']))->setAbsolute()->toString(); + $field = $this->getField($row_index, $this->options['description_field']); $item->description = is_array($field) ? $field : ['#markup' => $field]; + $item->elements = array( array('key' => 'pubDate', 'value' => $this->getField($row_index, $this->options['date_field'])), array( diff --git a/core/modules/views/src/Tests/Plugin/DisplayFeedTest.php b/core/modules/views/src/Tests/Plugin/DisplayFeedTest.php index 3f2cf21822bb..8032af333758 100644 --- a/core/modules/views/src/Tests/Plugin/DisplayFeedTest.php +++ b/core/modules/views/src/Tests/Plugin/DisplayFeedTest.php @@ -48,9 +48,13 @@ class DisplayFeedTest extends PluginTestBase { // Verify a title with HTML entities is properly escaped. $node_title = 'This "cool" & "neat" article\'s title'; - $node = $this->drupalCreateNode(array( - 'title' => $node_title - )); + $node = $this->drupalCreateNode([ + 'title' => $node_title, + 'body' => [0 => [ + 'value' => 'A paragraph', + 'format' => filter_default_format(), + ]], + ]); // Test the site name setting. $site_name = $this->randomMachineName(); @@ -60,6 +64,8 @@ class DisplayFeedTest extends PluginTestBase { $result = $this->xpath('//title'); $this->assertEqual($result[0], $site_name, 'The site title is used for the feed title.'); $this->assertEqual($result[1], $node_title, 'Node title with HTML entities displays correctly.'); + // Verify HTML is properly escaped in the description field. + $this->assertRaw('<p>A paragraph</p>'); $view = $this->container->get('entity.manager')->getStorage('view')->load('test_display_feed'); $display = &$view->getDisplay('feed_1'); @@ -101,12 +107,18 @@ class DisplayFeedTest extends PluginTestBase { // Verify a title with HTML entities is properly escaped. $node_title = 'This "cool" & "neat" article\'s title'; $this->drupalCreateNode(array( - 'title' => $node_title + 'title' => $node_title, + 'body' => [0 => [ + 'value' => 'A paragraph', + 'format' => filter_default_format(), + ]], )); $this->drupalGet('test-feed-display-fields.xml'); $result = $this->xpath('//title/a'); $this->assertEqual($result[0], $node_title, 'Node title with HTML entities displays correctly.'); + // Verify HTML is properly escaped in the description field. + $this->assertRaw('<p>A paragraph</p>'); } /** diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_feed.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_feed.yml index 2c35726c6077..26b3074405e1 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_feed.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_feed.yml @@ -1,8 +1,12 @@ langcode: en status: true dependencies: + config: + - core.entity_view_mode.node.teaser + - field.storage.node.body module: - node + - text - user id: test_display_feed label: test_display_feed @@ -41,6 +45,68 @@ display: plugin_id: field entity_type: node entity_field: title + body: + id: body + table: node__body + field: body + 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 + click_sort_column: value + type: text_default + settings: { } + 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 + plugin_id: field filters: status: expose: @@ -75,10 +141,21 @@ display: style: type: default title: test_display_feed + display_extenders: { } display_plugin: default display_title: Master id: default position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: + - 'config:field.storage.node.body' feed_1: display_options: displays: { } @@ -90,10 +167,20 @@ display: style: type: rss sitename_title: true + display_extenders: { } display_plugin: feed display_title: Feed id: feed_1 position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: + - 'config:field.storage.node.body' feed_2: display_options: displays: { } @@ -105,7 +192,7 @@ display: options: title_field: title link_field: title - description_field: title + description_field: body creator_field: title date_field: title guid_field_options: @@ -115,14 +202,35 @@ display: type: rss sitename_title: true display_description: '' + display_extenders: { } display_plugin: feed display_title: 'Feed with Fields' id: feed_2 position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: + - 'config:field.storage.node.body' page: display_options: path: test-feed-display + display_extenders: { } display_plugin: page - display_title: Page + display_title: 'Page' id: page position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: + - 'config:field.storage.node.body' diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index 96d4c7ba0ced..6a0a141d30c4 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -863,11 +863,14 @@ function template_preprocess_views_view_row_rss(&$variables) { $variables['title'] = $item->title; $variables['link'] = $item->link; - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = \Drupal::service('renderer'); - // We render the item description. It might contain entities, which attach rss - // elements via hook_entity_view, see comment_entity_view(). - $variables['description'] = is_array($item->description) ? $renderer->render($item->description) : $item->description; + // The description is the only place where we should find HTML. + // @see https://validator.w3.org/feed/docs/rss2.html#hrelementsOfLtitemgt + // If we have a render array, render it here and pass the result to the + // template, letting Twig autoescape it. + if (isset($item->description) && is_array($item->description)) { + $variables['description'] = (string) \Drupal::service('renderer')->render($item->description); + } + $variables['item_elements'] = array(); foreach ($item->elements as $element) { if (isset($element['attributes']) && is_array($element['attributes'])) {