core/core.services.yml | 5 +
core/lib/Drupal/Core/Ajax/AjaxResponse.php | 145 ----------------
.../Core/Ajax/AjaxResponseAttachmentsProcessor.php | 188 +++++++++++++++++++++
.../Drupal/Core/EventSubscriber/AjaxSubscriber.php | 56 +++++-
4 files changed, 248 insertions(+), 146 deletions(-)
diff --git a/core/core.services.yml b/core/core.services.yml
index 59c5421..50e921e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -816,6 +816,7 @@ services:
arguments: ['@resolver_manager.entity']
ajax_subscriber:
class: Drupal\Core\EventSubscriber\AjaxSubscriber
+ arguments: ['@ajax_response.attachments_processor']
tags:
- { name: event_subscriber }
form_ajax_subscriber:
@@ -975,6 +976,10 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@current_user']
+ ajax_response.attachments_processor:
+ class: Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor
+ tags:
+ arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer']
html_response.attachments_processor:
class: Drupal\Core\Render\HtmlResponseAttachmentsProcessor
tags:
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index 814345e..5f6f0d5 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -75,149 +75,4 @@ public function &getCommands() {
return $this->commands;
}
- /**
- * {@inheritdoc}
- *
- * Sets the response's data to be the array of AJAX commands.
- */
- public function prepare(Request $request) {
- $this->prepareResponse($request);
- return $this;
- }
-
- /**
- * Sets the rendered AJAX right before the response is prepared.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- * The request object.
- */
- public function prepareResponse(Request $request) {
- if ($this->data == '{}') {
- $this->setData($this->ajaxRender($request));
- }
-
- // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
- // for that browser, jquery.form submits requests containing a file upload
- // via an IFRAME rather than via XHR. Since the response is being sent to
- // an IFRAME, it must be formatted as HTML. Specifically:
- // - It must use the text/html content type or else the browser will
- // present a download prompt. Note: This applies to both file uploads
- // as well as any ajax request in a form with a file upload form.
- // - It must place the JSON data into a textarea to prevent browser
- // extensions such as Linkification and Skype's Browser Highlighter
- // from applying HTML transformations such as URL or phone number to
- // link conversions on the data values.
- //
- // Since this affects the format of the output, it could be argued that
- // this should be implemented as a separate Accept MIME type. However,
- // that would require separate variants for each type of AJAX request
- // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
- // this browser workaround is implemented via a GET or POST parameter.
- //
- // @see http://malsup.com/jquery/form/#file-upload
- // @see https://www.drupal.org/node/1009382
- // @see https://www.drupal.org/node/2339491
- // @see Drupal.ajax.prototype.beforeSend()
- $accept = $request->headers->get('accept');
-
- if (strpos($accept, 'text/html') !== FALSE) {
- $this->headers->set('Content-Type', 'text/html; charset=utf-8');
-
- // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
- // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
- // links. This corrupts the JSON response. Protect the integrity of the
- // JSON data by making it the value of a textarea.
- // @see http://malsup.com/jquery/form/#file-upload
- // @see https://www.drupal.org/node/1009382
- $this->setContent('');
- }
- }
-
- /**
- * Prepares the AJAX commands for sending back to the client.
- *
- * @param Request $request
- * The request object that the AJAX is responding to.
- *
- * @return array
- * An array of commands ready to be returned as JSON.
- */
- protected function ajaxRender(Request $request) {
- $ajax_page_state = $request->request->get('ajax_page_state');
-
- // Aggregate CSS/JS if necessary, but only during normal site operation.
- $config = \Drupal::config('system.performance');
- $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess');
- $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess');
-
- $attachments = $this->getAttachments();
-
- // Resolve the attached libraries into asset collections.
- $assets = new AttachedAssets();
- $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
- ->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [])
- ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
- $asset_resolver = \Drupal::service('asset.resolver');
- $css_assets = $asset_resolver->getCssAssets($assets, $optimize_css);
- list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
-
- // Render the HTML to load these files, and add AJAX commands to insert this
- // HTML in the page. Settings are handled separately, afterwards.
- $settings = [];
- if (isset($js_assets_header['drupalSettings'])) {
- $settings = $js_assets_header['drupalSettings']['data'];
- unset($js_assets_header['drupalSettings']);
- }
- if (isset($js_assets_footer['drupalSettings'])) {
- $settings = $js_assets_footer['drupalSettings']['data'];
- unset($js_assets_footer['drupalSettings']);
- }
-
- // Prepend commands to add the assets, preserving their relative order.
- $resource_commands = array();
- $renderer = $this->getRenderer();
- if (!empty($css_assets)) {
- $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets);
- $resource_commands[] = new AddCssCommand($renderer->render($css_render_array));
- }
- if (!empty($js_assets_header)) {
- $js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header);
- $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array));
- }
- if (!empty($js_assets_footer)) {
- $js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer);
- $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array));
- }
- foreach (array_reverse($resource_commands) as $resource_command) {
- $this->addCommand($resource_command, TRUE);
- }
-
- // Prepend a command to merge changes and additions to drupalSettings.
- if (!empty($settings)) {
- // During Ajax requests basic path-specific settings are excluded from
- // new drupalSettings values. The original page where this request comes
- // from already has the right values. An Ajax request would update them
- // with values for the Ajax request and incorrectly override the page's
- // values.
- // @see system_js_settings_alter()
- unset($settings['path']);
- $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
- }
-
- $commands = $this->commands;
- \Drupal::moduleHandler()->alter('ajax_render', $commands);
-
- return $commands;
- }
-
- /**
- * The renderer service.
- *
- * @return \Drupal\Core\Render\Renderer
- * The renderer service.
- */
- protected function getRenderer() {
- return \Drupal::service('renderer');
- }
-
}
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
index e69de29..79e8495 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -0,0 +1,188 @@
+assetResolver = $asset_resolver;
+ $this->config = $config_factory->get('system.performance');
+ $this->cssCollectionRenderer = $css_collection_renderer;
+ $this->jsCollectionRenderer = $js_collection_renderer;
+ $this->requestStack = $request_stack;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function processAttachments(AttachmentsResponseInterface $response) {
+ if (!$response instanceof AjaxResponse) {
+ throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.');
+ }
+
+ $request = $this->requestStack->getCurrentRequest();
+
+ if ($response->getContent() == '{}') {
+ $response->setData($this->buildAttachmentsCommands($response, $request));
+ }
+ }
+
+ /**
+ * Prepares the AJAX commands to attach assets.
+ *
+ * @param \Drupal\Core\Ajax\AjaxResponse $response
+ * The AJAX response to update.
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The request object that the AJAX is responding to.
+ *
+ * @return array
+ * An array of commands ready to be returned as JSON.
+ */
+ protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
+ $ajax_page_state = $request->request->get('ajax_page_state');
+
+ // Aggregate CSS/JS if necessary, but only during normal site operation.
+ $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
+ $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
+
+ $attachments = $response->getAttachments();
+
+ // Resolve the attached libraries into asset collections.
+ $assets = new AttachedAssets();
+ $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
+ ->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [])
+ ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
+ $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
+ list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
+
+ // Render the HTML to load these files, and add AJAX commands to insert this
+ // HTML in the page. Settings are handled separately, afterwards.
+ $settings = [];
+ if (isset($js_assets_header['drupalSettings'])) {
+ $settings = $js_assets_header['drupalSettings']['data'];
+ unset($js_assets_header['drupalSettings']);
+ }
+ if (isset($js_assets_footer['drupalSettings'])) {
+ $settings = $js_assets_footer['drupalSettings']['data'];
+ unset($js_assets_footer['drupalSettings']);
+ }
+
+ // Prepend commands to add the assets, preserving their relative order.
+ $resource_commands = array();
+ if (!empty($css_assets)) {
+ $css_render_array = $this->cssCollectionRenderer->render($css_assets);
+ $resource_commands[] = new AddCssCommand($this->renderer->render($css_render_array));
+ }
+ if (!empty($js_assets_header)) {
+ $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
+ $resource_commands[] = new PrependCommand('head', $this->renderer->render($js_header_render_array));
+ }
+ if (!empty($js_assets_footer)) {
+ $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
+ $resource_commands[] = new AppendCommand('body', $this->renderer->render($js_footer_render_array));
+ }
+ foreach (array_reverse($resource_commands) as $resource_command) {
+ $response->addCommand($resource_command, TRUE);
+ }
+
+ // Prepend a command to merge changes and additions to drupalSettings.
+ if (!empty($settings)) {
+ // During Ajax requests basic path-specific settings are excluded from
+ // new drupalSettings values. The original page where this request comes
+ // from already has the right values. An Ajax request would update them
+ // with values for the Ajax request and incorrectly override the page's
+ // values.
+ // @see system_js_settings_alter()
+ unset($settings['path']);
+ $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
+ }
+
+ $commands = $response->getCommands();
+ \Drupal::moduleHandler()->alter('ajax_render', $commands);
+
+ return $commands;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
index 8996b5d..a6133d6 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
@@ -9,6 +9,7 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -20,6 +21,23 @@
class AjaxSubscriber implements EventSubscriberInterface {
/**
+ * The AJAX response attachments processor service.
+ *
+ * @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
+ */
+ protected $ajaxResponseAttachmentsProcessor;
+
+ /**
+ * Constructs an AjaxSubscriber object.
+ *
+ * @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $ajax_response_attachments_processor
+ * The AJAX response attachments processor service.
+ */
+ public function __construct(AttachmentsResponseProcessorInterface $ajax_response_attachments_processor) {
+ $this->ajaxResponseAttachmentsProcessor = $ajax_response_attachments_processor;
+ }
+
+ /**
* Sets the AJAX HTML IDs from the current request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
@@ -38,7 +56,43 @@ public function onRequest(GetResponseEvent $event) {
public function onResponse(FilterResponseEvent $event) {
$response = $event->getResponse();
if ($response instanceof AjaxResponse) {
- $response->prepareResponse($event->getRequest());
+ $this->ajaxResponseAttachmentsProcessor->processAttachments($response);
+
+ // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
+ // for that browser, jquery.form submits requests containing a file upload
+ // via an IFRAME rather than via XHR. Since the response is being sent to
+ // an IFRAME, it must be formatted as HTML. Specifically:
+ // - It must use the text/html content type or else the browser will
+ // present a download prompt. Note: This applies to both file uploads
+ // as well as any ajax request in a form with a file upload form.
+ // - It must place the JSON data into a textarea to prevent browser
+ // extensions such as Linkification and Skype's Browser Highlighter
+ // from applying HTML transformations such as URL or phone number to
+ // link conversions on the data values.
+ //
+ // Since this affects the format of the output, it could be argued that
+ // this should be implemented as a separate Accept MIME type. However,
+ // that would require separate variants for each type of AJAX request
+ // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
+ // this browser workaround is implemented via a GET or POST parameter.
+ //
+ // @see http://malsup.com/jquery/form/#file-upload
+ // @see https://www.drupal.org/node/1009382
+ // @see https://www.drupal.org/node/2339491
+ // @see Drupal.ajax.prototype.beforeSend()
+ $accept = $event->getRequest()->headers->get('accept');
+
+ if (strpos($accept, 'text/html') !== FALSE) {
+ $response->headers->set('Content-Type', 'text/html; charset=utf-8');
+
+ // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
+ // and Skype's Browser Highlighter, convert URLs, phone numbers, etc.
+ // into links. This corrupts the JSON response. Protect the integrity of
+ // the JSON data by making it the value of a textarea.
+ // @see http://malsup.com/jquery/form/#file-upload
+ // @see https://www.drupal.org/node/1009382
+ $response->setContent('');
+ }
}
}