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