From f7f7f37305012b1694d85af54309d81bf9e28504 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Tue, 18 Jun 2013 09:19:24 +0200
Subject: [PATCH] Issue #1998698 by larowlan, jibran, quicksketch: Allow Dialog
 Controller to work with form/entity form routes.

---
 .../Drupal/Core/Controller/AjaxController.php |  2 -
 .../Core/Controller/DialogController.php      | 31 +++----
 .../Enhancer/ContentControllerEnhancer.php    | 13 ++-
 .../Drupal/system/Tests/Ajax/DialogTest.php   | 82 +++++++++++++++++++
 .../modules/ajax_test/ajax_test.info.yml      |  2 +
 .../tests/modules/ajax_test/ajax_test.module  | 31 +++++++
 .../modules/ajax_test/ajax_test.routing.yml   |  6 ++
 .../lib/Drupal/ajax_test/AjaxTestForm.php     | 56 +++++++++++++
 8 files changed, 204 insertions(+), 19 deletions(-)
 create mode 100644 core/modules/system/tests/modules/ajax_test/lib/Drupal/ajax_test/AjaxTestForm.php

diff --git a/core/lib/Drupal/Core/Controller/AjaxController.php b/core/lib/Drupal/Core/Controller/AjaxController.php
index 411311ebff55..b4dc5a10c555 100644
--- a/core/lib/Drupal/Core/Controller/AjaxController.php
+++ b/core/lib/Drupal/Core/Controller/AjaxController.php
@@ -77,5 +77,3 @@ public function content(Request $request, $_content) {
     return $response;
   }
 }
-
-
diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php
index 440fb15f4949..365c196f6f94 100644
--- a/core/lib/Drupal/Core/Controller/DialogController.php
+++ b/core/lib/Drupal/Core/Controller/DialogController.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\OpenDialogCommand;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 
@@ -25,9 +26,10 @@ class DialogController {
   protected $httpKernel;
 
   /**
-   * Constructs a new HtmlPageController.
+   * Constructs a new DialogController.
    *
    * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
+   *   The kernel.
    */
   public function __construct(HttpKernelInterface $kernel) {
     $this->httpKernel = $kernel;
@@ -38,13 +40,11 @@ public function __construct(HttpKernelInterface $kernel) {
    *
    * @param \Symfony\Component\HttpFoundation\RequestRequest $request
    *   The request object.
-   * @param callable $content
-   *   The body content callable that contains the body region of this page.
    *
    * @return \Symfony\Component\HttpFoundation\Response
    *   A response object.
    */
-  protected function forward(Request $request, $content) {
+  protected function forward(Request $request) {
     // @todo When we have a Generator, we can replace the forward() call with
     // a render() call, which would handle ESI and hInclude as well.  That will
     // require an _internal route.  For examples, see:
@@ -54,12 +54,16 @@ protected function forward(Request $request, $content) {
     // We need to clean up the derived information and such so that the
     // subrequest can be processed properly without leaking data through.
     $attributes->remove('system_path');
+    $attributes->set('dialog', TRUE);
 
     // Remove the accept header so the subrequest does not end up back in this
     // controller.
     $request->headers->remove('accept');
+    // Remove the X-Requested-With header so the subrequest is not mistaken for
+    // an ajax request.
+    $request->headers->remove('x-requested-with');
 
-    return $this->httpKernel->forward($content, $attributes->all(), $request->query->all());
+    return $this->httpKernel->forward(NULL, $attributes->all(), $request->query->all());
   }
 
   /**
@@ -67,14 +71,12 @@ protected function forward(Request $request, $content) {
    *
    * @param \Symfony\Component\HttpFoundation\RequestRequest $request
    *   The request object.
-   * @param callable $_content
-   *   The body content callable that contains the body region of this page.
    *
    * @return \Drupal\Core\Ajax\AjaxResponse
    *   AjaxResponse to return the content wrapper in a modal dialog.
    */
-  public function modal(Request $request, $_content) {
-    return $this->dialog($request, $_content, TRUE);
+  public function modal(Request $request) {
+    return $this->dialog($request, TRUE);
   }
 
   /**
@@ -82,16 +84,14 @@ public function modal(Request $request, $_content) {
    *
    * @param \Symfony\Component\HttpFoundation\RequestRequest $request
    *   The request object.
-   * @param callable $_content
-   *   The body content callable that contains the body region of this page.
    * @param bool $modal
    *   (optional) TRUE to render a modal dialog. Defaults to FALSE.
    *
    * @return \Drupal\Core\Ajax\AjaxResponse
    *   AjaxResponse to return the content wrapper in a dialog.
    */
-  public function dialog(Request $request, $_content, $modal = FALSE) {
-    $subrequest = $this->forward($request, $_content);
+  public function dialog(Request $request, $modal = FALSE) {
+    $subrequest = $this->forward($request);
     if ($subrequest->isOk()) {
       $content = $subrequest->getContent();
       // @todo Remove use of drupal_get_title() when
@@ -120,8 +120,9 @@ public function dialog(Request $request, $_content, $modal = FALSE) {
           unset($options['target']);
         }
         else {
-          // Generate a target based on the controller.
-          $target = '#drupal-dialog-' . drupal_html_id(drupal_clean_css_identifier(drupal_strtolower($_content)));
+          // Generate a target based on the route id.
+          $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+          $target = '#' . drupal_html_id("drupal-dialog-$route_name");
         }
       }
       $response->addCommand(new OpenDialogCommand($target, $title, $content, $options));
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
index c355cc76ae5d..e088d4447525 100644
--- a/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
+++ b/core/lib/Drupal/Core/Routing/Enhancer/ContentControllerEnhancer.php
@@ -48,12 +48,21 @@ public function __construct(ContentNegotiation $negotiation) {
    * {@inheritdoc}
    */
   public function enhance(array $defaults, Request $request) {
-    if (empty($defaults['_controller']) && !empty($defaults['_content'])) {
-      $type = $this->negotiation->getContentType($request);
+    // If no controller is set and either _content is set or the request is
+    // for a dialog or modal, then enhance.
+    if (empty($defaults['_controller']) &&
+      ($type = $this->negotiation->getContentType($request)) &&
+      (!empty($defaults['_content']) ||
+      in_array($type, array('drupal_dialog', 'drupal_modal')))) {
       if (isset($this->types[$type])) {
         $defaults['_controller'] = $this->types[$type];
       }
     }
+    // When the dialog attribute is TRUE this is a DialogController sub-request.
+    // Route the sub-request to the _content callable.
+    if ($request->attributes->get('dialog') && !empty($defaults['_content'])) {
+      $defaults['_controller'] = $defaults['_content'];
+    }
     return $defaults;
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
index 64780fb15b3d..5af8337dd33e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
@@ -7,11 +7,20 @@
 
 namespace Drupal\system\Tests\Ajax;
 
+use Drupal\ajax_test\AjaxTestForm;
+
 /**
  * Tests use of dialogs as wrappers for Ajax responses.
  */
 class DialogTest extends AjaxTestBase {
 
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('ajax_test', 'ajax_forms_test', 'contact');
+
   /**
    * Declares test info.
    */
@@ -27,6 +36,7 @@ public static function getInfo() {
    * Test sending non-JS and AJAX requests to open and manipulate modals.
    */
   public function testDialog() {
+    $this->drupalLogin($this->drupalCreateUser(array('administer contact forms')));
     // Ensure the elements render without notices or exceptions.
     $this->drupalGet('ajax-test/dialog');
 
@@ -43,6 +53,24 @@ public function testDialog() {
         'title' => 'AJAX Dialog contents',
       ),
     );
+    $form_expected_response = array(
+      'command' => 'openDialog',
+      'selector' => '#drupal-modal',
+      'settings' => NULL,
+      'dialogOptions' => array(
+        'modal' => TRUE,
+        'title' => 'Ajax Form contents',
+      ),
+    );
+    $entity_form_expected_response = array(
+      'command' => 'openDialog',
+      'selector' => '#drupal-modal',
+      'settings' => NULL,
+      'dialogOptions' => array(
+        'modal' => TRUE,
+        'title' => 'Home',
+      ),
+    );
     $normal_expected_response = array(
       'command' => 'openDialog',
       'selector' => '#ajax-test-dialog-wrapper-1',
@@ -53,6 +81,16 @@ public function testDialog() {
         'title' => 'AJAX Dialog contents',
       ),
     );
+    $no_target_expected_response = array(
+      'command' => 'openDialog',
+      'selector' => '#drupal-dialog-ajax-test-dialog-contents',
+      'settings' => NULL,
+      'data' => $dialog_contents,
+      'dialogOptions' => array(
+        'modal' => FALSE,
+        'title' => 'AJAX Dialog contents',
+      ),
+    );
     $close_expected_response = array(
       'command' => 'closeDialog',
       'selector' => '#ajax-test-dialog-wrapper-1',
@@ -83,6 +121,18 @@ public function testDialog() {
     ));
     $this->assertEqual($normal_expected_response, $ajax_result[3], 'Normal dialog JSON response matches.');
 
+    // Emulate going to the JS version of the page and check the JSON response.
+    // This needs to use WebTestBase::drupalPostAJAX() so that the correct
+    // dialog options are sent.
+    $ajax_result = $this->drupalPostAJAX('ajax-test/dialog', array(
+        // We have to mock a form element to make drupalPost submit from a link.
+        'textfield' => 'test',
+      ), array(), 'ajax-test/dialog-contents', array(), array('Accept: application/vnd.drupal-dialog'), NULL, array(
+      // Don't send a target.
+      'submit' => array()
+    ));
+    $this->assertEqual($no_target_expected_response, $ajax_result[3], 'Normal dialog with no target JSON response matches.');
+
     // Emulate closing the dialog via an AJAX request. There is no non-JS
     // version of this test.
     $ajax_result = $this->drupalGetAJAX('ajax-test/dialog-close');
@@ -107,6 +157,38 @@ public function testDialog() {
     // Abbreviated test for "normal" dialogs, testing only the difference.
     $ajax_result = $this->drupalPostAJAX('ajax-test/dialog', array(), 'button2');
     $this->assertEqual($normal_expected_response, $ajax_result[3], 'POST request normal dialog JSON response matches.');
+
+    // Check that requesting a form dialog without JS goes to a page.
+    $this->drupalGet('ajax-test/dialog-form');
+    // Check we get a chunk of the code, we can't test the whole form as form
+    // build id and token with be different.
+    $form = $this->xpath("//form[@id='ajax-test-form']");
+    $this->assertTrue(!empty($form), 'Non-JS form page present.');
+
+    // Emulate going to the JS version of the form and check the JSON response.
+    $ajax_result = $this->drupalGetAJAX('ajax-test/dialog-form', array(), array('Accept: application/vnd.drupal-modal'));
+    $this->drupalSetContent($ajax_result[1]['data']);
+    // Remove the data, the form build id and token will never match.
+    unset($ajax_result[1]['data']);
+    $form = $this->xpath("//form[@id='ajax-test-form']");
+    $this->assertTrue(!empty($form), 'Modal dialog JSON contains form.');
+    $this->assertEqual($form_expected_response, $ajax_result[1]);
+
+    // Check that requesting an entity form dialog without JS goes to a page.
+    $this->drupalGet('admin/structure/contact/add');
+    // Check we get a chunk of the code, we can't test the whole form as form
+    // build id and token with be different.
+    $form = $this->xpath("//form[@id='contact-category-add-form']");
+    $this->assertTrue(!empty($form), 'Non-JS entity form page present.');
+
+    // Emulate going to the JS version of the form and check the JSON response.
+    $ajax_result = $this->drupalGetAJAX('admin/structure/contact/add', array(), array('Accept: application/vnd.drupal-modal'));
+    $this->drupalSetContent($ajax_result[1]['data']);
+    // Remove the data, the form build id and token will never match.
+    unset($ajax_result[1]['data']);
+    $form = $this->xpath("//form[@id='contact-category-add-form']");
+    $this->assertTrue(!empty($form), 'Modal dialog JSON contains entity form.');
+    $this->assertEqual($entity_form_expected_response, $ajax_result[1]);
   }
 
 }
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml
index 0d9c75c459db..153eee9980b2 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.info.yml
@@ -4,4 +4,6 @@ description: 'Support module for AJAX framework tests.'
 package: Testing
 version: VERSION
 core: 8.x
+dependencies:
+  - contact
 hidden: true
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index 657af2d0a991..e358018a9bbc 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -160,6 +160,37 @@ function ajax_test_dialog() {
           'class' => array('use-ajax'),
         ),
       ),
+      'link5' => array(
+        'title' => 'Link 5 (form)',
+        'href' => 'ajax-test/dialog-form',
+        'attributes' => array(
+          'class' => array('use-ajax'),
+          'data-accepts' => 'application/vnd.drupal-modal',
+        ),
+      ),
+      'link6' => array(
+        'title' => 'Link 6 (entity form)',
+        'href' => 'admin/structure/contact/add',
+        'attributes' => array(
+          'class' => array('use-ajax'),
+          'data-accepts' => 'application/vnd.drupal-modal',
+          'data-dialog-options' => json_encode(array(
+            'width' => 800,
+            'height' => 500,
+          ))
+        ),
+      ),
+      'link7' => array(
+        'title' => 'Link 7 (non-modal, no target)',
+        'href' => 'ajax-test/dialog-contents',
+        'attributes' => array(
+          'class' => array('use-ajax'),
+          'data-accepts' => 'application/vnd.drupal-dialog',
+          'data-dialog-options' => json_encode(array(
+            'width' => 800,
+          ))
+        ),
+      ),
     ),
   );
   return $build;
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
index 8379d64042ad..04a0f6d6ab9d 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
@@ -4,3 +4,9 @@ ajax_test_dialog_contents:
     _content: '\Drupal\ajax_test\AjaxTestController::dialogContents'
   requirements:
     _access: 'TRUE'
+ajax_test_dialog_form:
+  pattern: ajax-test/dialog-form
+  defaults:
+    _form: '\Drupal\ajax_test\AjaxTestForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/ajax_test/lib/Drupal/ajax_test/AjaxTestForm.php b/core/modules/system/tests/modules/ajax_test/lib/Drupal/ajax_test/AjaxTestForm.php
new file mode 100644
index 000000000000..987b67e2a773
--- /dev/null
+++ b/core/modules/system/tests/modules/ajax_test/lib/Drupal/ajax_test/AjaxTestForm.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ajax_test\AjaxTestForm.
+ */
+
+namespace Drupal\ajax_test;
+
+use Drupal\Core\Form\FormInterface;
+
+/**
+ * Dummy form for testing DialogController with _form routes.
+ */
+class AjaxTestForm implements FormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'ajax_test_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+
+    drupal_set_title(t('Ajax Form contents'));
+
+    $form['#action'] = url('ajax-test/dialog');
+    $form['#cache'] = TRUE;
+
+    $form['description'] = array(
+      '#markup' => '<p>' . t("Ajax Form contents description.") . '</p>',
+    );
+
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Do it'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {}
+
+}
-- 
GitLab