From 8f3e25cdc5b9929ca28580e3959cb860c678fe85 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Thu, 27 Oct 2016 21:54:32 -0700
Subject: [PATCH] Issue #2207247 by larowlan, sasanikolic, olli, s_leu, Berdir,
 joelpittet, mrjmd, rpayanm, dawehner, Truptti, nod_, alexpott, Wim Leers:
 Dialog titles double escaped for views handlers and delete forms

---
 .../lib/Drupal/Core/Ajax/OpenDialogCommand.php |  3 +++
 .../src/Tests/Ajax/OffCanvasDialogTest.php     |  2 +-
 .../system/src/Tests/Ajax/DialogTest.php       | 11 ++++++++---
 .../src/Controller/AjaxTestController.php      |  2 +-
 .../ajax_test/src/Form/AjaxTestDialogForm.php  |  2 +-
 .../modules/views_ui/src/Tests/FieldUITest.php | 18 ++++++++++++++++++
 6 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
index 5c5be4cfb728..30b65fac4413 100644
--- a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Core\Ajax;
 
+use Drupal\Component\Render\PlainTextOutput;
+
 /**
  * Defines an AJAX command to open certain content in a dialog.
  *
@@ -69,6 +71,7 @@ class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsIn
    *   populated automatically from the current request.
    */
   public function __construct($selector, $title, $content, array $dialog_options = array(), $settings = NULL) {
+    $title = PlainTextOutput::renderFromHtml($title);
     $dialog_options += array('title' => $title);
     $this->selector = $selector;
     $this->content = $content;
diff --git a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
index 1e5b0d195102..f3510427bd10 100644
--- a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
+++ b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
@@ -39,7 +39,7 @@ public function testDialog() {
       'data' => $dialog_contents,
       'dialogOptions' =>
         [
-          'title' => 'AJAX Dialog contents',
+          'title' => 'AJAX Dialog & contents',
           'modal' => FALSE,
           'autoResize' => FALSE,
           'resizable' => 'w',
diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php
index 80b465067265..254f01d33ae0 100644
--- a/core/modules/system/src/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/src/Tests/Ajax/DialogTest.php
@@ -39,7 +39,7 @@ public function testDialog() {
       'data' => $dialog_contents,
       'dialogOptions' => array(
         'modal' => TRUE,
-        'title' => 'AJAX Dialog contents',
+        'title' => 'AJAX Dialog & contents',
       ),
     );
     $form_expected_response = array(
@@ -67,7 +67,7 @@ public function testDialog() {
       'data' => $dialog_contents,
       'dialogOptions' => array(
         'modal' => FALSE,
-        'title' => 'AJAX Dialog contents',
+        'title' => 'AJAX Dialog & contents',
       ),
     );
     $no_target_expected_response = array(
@@ -77,7 +77,7 @@ public function testDialog() {
       'data' => $dialog_contents,
       'dialogOptions' => array(
         'modal' => FALSE,
-        'title' => 'AJAX Dialog contents',
+        'title' => 'AJAX Dialog & contents',
       ),
     );
     $close_expected_response = array(
@@ -97,6 +97,9 @@ public function testDialog() {
     // Emulate going to the JS version of the page and check the JSON response.
     $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
     $this->assertEqual($modal_expected_response, $ajax_result[3], 'Modal dialog JSON response matches.');
+    // Test the HTML escaping of & character.
+    $this->assertEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog & contents');
+    $this->assertNotEqual($ajax_result[3]['dialogOptions']['title'], 'AJAX Dialog &amp; contents');
 
     // Check that requesting a "normal" dialog without JS goes to a page.
     $this->drupalGet('ajax-test/dialog-contents');
@@ -152,6 +155,8 @@ public function testDialog() {
 
     // Check that the response matches the expected value.
     $this->assertEqual($modal_expected_response, $ajax_result[4], 'POST request modal dialog JSON response matches.');
+    // Test the HTML escaping of & character.
+    $this->assertNotEqual($ajax_result[4]['dialogOptions']['title'], 'AJAX Dialog &amp; contents');
 
     // Abbreviated test for "normal" dialogs, testing only the difference.
     $ajax_result = $this->drupalPostAjaxForm('ajax-test/dialog', array(), 'button2');
diff --git a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
index b618f0af7247..63cc0abf1634 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php
@@ -23,7 +23,7 @@ class AjaxTestController {
   public static function dialogContents() {
     // This is a regular render array; the keys do not have special meaning.
     $content = array(
-      '#title' => 'AJAX Dialog contents',
+      '#title' => '<em>AJAX Dialog & contents</em>',
       'content' => array(
         '#markup' => 'Example message',
       ),
diff --git a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php
index 304e2bbafa84..a97ed9525bca 100644
--- a/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php
+++ b/core/modules/system/tests/modules/ajax_test/src/Form/AjaxTestDialogForm.php
@@ -93,7 +93,7 @@ public function nonModal(&$form, FormStateInterface $form_state) {
   protected function dialog($is_modal = FALSE) {
     $content = AjaxTestController::dialogContents();
     $response = new AjaxResponse();
-    $title = $this->t('AJAX Dialog contents');
+    $title = $this->t('AJAX Dialog & contents');
 
     // Attach the library necessary for using the Open(Modal)DialogCommand and
     // set the attachments for this Ajax response.
diff --git a/core/modules/views_ui/src/Tests/FieldUITest.php b/core/modules/views_ui/src/Tests/FieldUITest.php
index 5b79da9b4ca5..8c486d8b32ff 100644
--- a/core/modules/views_ui/src/Tests/FieldUITest.php
+++ b/core/modules/views_ui/src/Tests/FieldUITest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\views_ui\Tests;
 
+use Drupal\Component\Serialization\Json;
 use Drupal\views\Views;
 
 /**
@@ -55,6 +56,23 @@ public function testFieldUI() {
 
     $result = $this->xpath('//details[@id="edit-options-more"]');
     $this->assertEqual(empty($result), TRUE, "Container 'more' is empty and should not be displayed.");
+
+    // Ensure that dialog titles are not escaped.
+    $edit_groupby_url = 'admin/structure/views/nojs/handler/test_view/default/field/name';
+    $this->assertNoLinkByHref($edit_groupby_url, 0, 'No aggregation link found.');
+
+    // Enable aggregation on the view.
+    $edit = array(
+      'group_by' => TRUE,
+    );
+    $this->drupalPostForm('/admin/structure/views/nojs/display/test_view/default/group_by', $edit, t('Apply'));
+
+    $this->assertLinkByHref($edit_groupby_url, 0, 'Aggregation link found.');
+
+    $edit_handler_url = '/admin/structure/views/ajax/handler-group/test_view/default/field/name';
+    $this->drupalGet($edit_handler_url);
+    $data = Json::decode($this->getRawContent());
+    $this->assertEqual($data[3]['dialogOptions']['title'], 'Configure aggregation settings for field Views test: Name');
   }
 
   /**
-- 
GitLab