Commit 42c693a9 authored by alexpott's avatar alexpott

Issue #2407195 by Wim Leers, Fabianx, joelpittet, lauriii, Crell: Move...

Issue #2407195 by Wim Leers, Fabianx, joelpittet, lauriii, Crell: Move attachment processing to services and per-type response subclasses
parent c65b82f4
......@@ -85,8 +85,10 @@ function authorize_access_allowed(Request $request) {
$content = [];
$show_messages = TRUE;
$response = new Response();
if (authorize_access_allowed($request)) {
$is_allowed = authorize_access_allowed($request);
// Build content.
if ($is_allowed) {
// Load both the Form API and Batch API.
require_once __DIR__ . '/includes/form.inc';
require_once __DIR__ . '/includes/batch.inc';
......@@ -152,16 +154,16 @@ function authorize_access_allowed(Request $request) {
$show_messages = !(($batch = batch_get()) && isset($batch['running']));
}
else {
$response->setStatusCode(403);
\Drupal::logger('access denied')->warning('authorize.php');
$page_title = t('Access denied');
$content = ['#markup' => t('You are not allowed to access this page.')];
}
if (!empty($content)) {
$response->headers->set('Content-Type', 'text/html; charset=utf-8');
$response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($content, $page_title, 'maintenance_page', array(
'#show_messages' => $show_messages,
)));
$response->send();
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
$response = $bare_html_page_renderer->renderBarePage($content, $page_title, 'maintenance_page', array(
'#show_messages' => $show_messages,
));
if (!$is_allowed) {
$response->setStatusCode(403);
}
$response->send();
......@@ -814,8 +814,9 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@resolver_manager.entity']
ajax_subscriber:
class: Drupal\Core\EventSubscriber\AjaxSubscriber
ajax_response.subscriber:
class: Drupal\Core\EventSubscriber\AjaxResponseSubscriber
arguments: ['@ajax_response.attachments_processor']
tags:
- { name: event_subscriber }
form_ajax_subscriber:
......@@ -903,7 +904,7 @@ services:
arguments: ['@router', '@router.request_context', NULL, '@request_stack']
bare_html_page_renderer:
class: Drupal\Core\Render\BareHtmlPageRenderer
arguments: ['@renderer']
arguments: ['@renderer', '@html_response.attachments_processor']
lazy: true
private_key:
class: Drupal\Core\PrivateKey
......@@ -975,6 +976,19 @@ 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', '@module_handler']
html_response.attachments_processor:
class: Drupal\Core\Render\HtmlResponseAttachmentsProcessor
tags:
arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer']
html_response.subscriber:
class: Drupal\Core\EventSubscriber\HtmlResponseSubscriber
tags:
- { name: event_subscriber }
arguments: ['@html_response.attachments_processor']
finish_response_subscriber:
class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
tags:
......
......@@ -137,9 +137,14 @@ function _batch_progress_page() {
// additional HTML output by PHP shows up inside the page rather than below
// it. While this causes invalid HTML, the same would be true if we didn't,
// as content is not allowed to appear after </html> anyway.
$fallback = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array(
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
$response = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', array(
'#show_messages' => FALSE,
));
// Just use the content of the response.
$fallback = $response->getContent();
list($fallback) = explode('<!--partial-->', $fallback);
print $fallback;
......
......@@ -9,7 +9,6 @@
use Drupal\Component\Utility\Xss;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Response;
/**
* Maps PHP error constants to watchdog severity levels.
......@@ -241,12 +240,11 @@ function _drupal_log_error($error, $fatal = FALSE) {
'#markup' => $message,
);
install_display_output($output, $GLOBALS['install_state']);
}
else {
$output = \Drupal::service('bare_html_page_renderer')->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page');
exit;
}
$response = new Response($output, 500);
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
$response = $bare_html_page_renderer->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page');
$response->setStatusCode(500, '500 Service unavailable (with message)');
// An exception must halt script execution.
$response->send();
......
......@@ -984,7 +984,8 @@ function install_display_output($output, $install_state) {
$regions['sidebar_first'] = $task_list;
}
$response = new Response();
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
$response = $bare_html_page_renderer->renderBarePage($output, $output['#title'], 'install_page', $regions);
$default_headers = array(
'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
......@@ -992,7 +993,6 @@ function install_display_output($output, $install_state) {
'ETag' => '"' . REQUEST_TIME . '"',
);
$response->headers->add($default_headers);
$response->setContent(\Drupal::service('bare_html_page_renderer')->renderBarePage($output, $output['#title'], 'install_page', $regions));
$response->send();
exit;
}
......
......@@ -9,21 +9,17 @@
*/
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageException;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionNameLengthException;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ThemeSettings;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Render\Element;
use Symfony\Component\HttpFoundation\Request;
/**
* @defgroup content_flags Content markers
......@@ -1308,41 +1304,23 @@ function template_preprocess_html(&$variables) {
// @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
$variables['head_title_array'] = $head_title;
// Collect all attachments. This must happen in the preprocess function for
// #type => html, to ensure that attachments added in #pre_render callbacks
// for #type => html are included.
$attached = $variables['html']['#attached'];
$attached = drupal_merge_attached($attached, $variables['page']['#attached']);
if (isset($variables['page_top'])) {
$attached = drupal_merge_attached($attached, $variables['page_top']['#attached']);
// Create placeholder strings for these keys.
// @see \Drupal\Core\Render\HtmlResponseSubscriber
$types = [
'styles',
'scripts',
'scripts_bottom',
'head',
];
$token = Crypt::randomBytesBase64(55);
foreach ($types as $type) {
$placeholder = SafeMarkup::format('<drupal-html-response-attachment-placeholder type="@type" token="@token"></drupal-html-response-attachment-placeholder>', [
'@type' => $type,
'@token' => $token,
]);
$variables[$type]['#markup'] = $placeholder;
$variables[$type]['#attached']['html_response_placeholders'][$type] = $placeholder;
}
if (isset($variables['page_bottom'])) {
$attached = drupal_merge_attached($attached, $variables['page_bottom']['#attached']);
}
// Render the attachments into HTML markup to be used directly in the template
// for #type => html: html.html.twig.
$all_attached = ['#attached' => $attached];
$assets = AttachedAssets::createFromRenderArray($all_attached);
// Take Ajax page state into account, to allow for something like Turbolinks
// to be implemented without altering core.
// @see https://github.com/rails/turbolinks/
$ajax_page_state = \Drupal::request()->request->get('ajax_page_state');
$assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []);
// Optimize CSS/JS if necessary, but only during normal site operation.
$optimize_css = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess');
$optimize_js = !defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('js.preprocess');
// Render the asset collections.
$asset_resolver = \Drupal::service('asset.resolver');
$variables['styles'] = \Drupal::service('asset.css.collection_renderer')->render($asset_resolver->getCssAssets($assets, $optimize_css));
list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
$variables['scripts'] = $js_collection_renderer->render($js_assets_header);
$variables['scripts_bottom'] = $js_collection_renderer->render($js_assets_footer);
// Handle all non-asset attachments.
drupal_process_attached($all_attached);
$variables['head'] = drupal_get_html_head(FALSE);
}
/**
......
......@@ -10,6 +10,8 @@
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\AttachmentsTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
......@@ -20,7 +22,9 @@
*
* @ingroup ajax
*/
class AjaxResponse extends JsonResponse {
class AjaxResponse extends JsonResponse implements AttachmentsInterface {
use AttachmentsTrait;
/**
* The array of ajax commands.
......@@ -29,32 +33,6 @@ class AjaxResponse extends JsonResponse {
*/
protected $commands = array();
/**
* The attachments for this Ajax response.
*
* @var array
*/
protected $attachments = [
'library' => [],
'drupalSettings' => [],
];
/**
* Sets attachments for this Ajax response.
*
* When this Ajax response is rendered, it will take care of generating the
* necessary Ajax commands, if any.
*
* @param array $attachments
* An #attached array.
*
* @return $this
*/
public function setAttachments(array $attachments) {
$this->attachments = $attachments;
return $this;
}
/**
* Add an AJAX command to the response.
*
......@@ -80,7 +58,7 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) {
'library' => $assets->getLibraries(),
'drupalSettings' => $assets->getSettings(),
];
$attachments = BubbleableMetadata::mergeAttachments($this->attachments, $attachments);
$attachments = BubbleableMetadata::mergeAttachments($this->getAttachments(), $attachments);
$this->setAttachments($attachments);
}
......@@ -97,147 +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('<textarea>' . $this->data . '</textarea>');
}
}
/**
* 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');
// Resolve the attached libraries into asset collections.
$assets = new AttachedAssets();
$assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : [])
->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
->setSettings(isset($this->attachments['drupalSettings']) ? $this->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');
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor.
*/
namespace Drupal\Core\Ajax;
use Drupal\Core\Asset\AssetCollectionRendererInterface;
use Drupal\Core\Asset\AssetResolverInterface;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Processes attachments of AJAX responses.
*
* @see \Drupal\Core\Ajax\AjaxResponse
* @see \Drupal\Core\Render\MainContent\AjaxRenderer
*/
class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
/**
* The asset resolver service.
*
* @var \Drupal\Core\Asset\AssetResolverInterface
*/
protected $assetResolver;
/**
* A config object for the system performance configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* The CSS asset collection renderer service.
*
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
*/
protected $cssCollectionRenderer;
/**
* The JS asset collection renderer service.
*
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
*/
protected $jsCollectionRenderer;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a AjaxResponseAttachmentsProcessor object.
*
* @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
* An asset resolver.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A config factory for retrieving required config objects.
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
* The CSS asset collection renderer.
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
* The JS asset collection renderer.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
$this->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;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function processAttachments(AttachmentsInterface $response) {
// @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
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));
}
return $response;
}
/**
* 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['libraries']) ? 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 ($css_assets) {
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
$resource_commands[] = new AddCssCommand($this->renderer->render($css_render_array));
}
if ($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 ($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();
$this->moduleHandler->alter('ajax_render', $commands);
return $commands;
}
}
......@@ -2,22 +2,40 @@
/**
* @file
* Contains \Drupal\Core\EventSubscriber\AjaxSubscriber.
* Contains \Drupal\Core\EventSubscriber\AjaxResponseSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
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;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Subscribes to set AJAX HTML IDs and prepare AJAX responses.
* Response subscriber to handle AJAX responses.
*/
class AjaxSubscriber implements EventSubscriberInterface {
class AjaxResponseSubscriber implements EventSubscriberInterface {
/**
* The AJAX response attachments processor service.
*
* @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
*/
protected $ajaxResponseAttachmentsProcessor;
/**
* Constructs an AjaxResponseSubscriber 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;
}
/**
* Request parameter to indicate that a request is a Drupal Ajax request.
......@@ -46,7 +64,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.