AjaxResponseSubscriber.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. <?php
  2. namespace Drupal\Core\EventSubscriber;
  3. use Drupal\Component\Utility\Html;
  4. use Drupal\Core\Ajax\AjaxResponse;
  5. use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
  6. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  7. use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
  8. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  9. use Symfony\Component\HttpKernel\KernelEvents;
  10. /**
  11. * Response subscriber to handle AJAX responses.
  12. */
  13. class AjaxResponseSubscriber implements EventSubscriberInterface {
  14. /**
  15. * The AJAX response attachments processor service.
  16. *
  17. * @var \Drupal\Core\Render\AttachmentsResponseProcessorInterface
  18. */
  19. protected $ajaxResponseAttachmentsProcessor;
  20. /**
  21. * Constructs an AjaxResponseSubscriber object.
  22. *
  23. * @param \Drupal\Core\Render\AttachmentsResponseProcessorInterface $ajax_response_attachments_processor
  24. * The AJAX response attachments processor service.
  25. */
  26. public function __construct(AttachmentsResponseProcessorInterface $ajax_response_attachments_processor) {
  27. $this->ajaxResponseAttachmentsProcessor = $ajax_response_attachments_processor;
  28. }
  29. /**
  30. * Request parameter to indicate that a request is a Drupal Ajax request.
  31. */
  32. const AJAX_REQUEST_PARAMETER = '_drupal_ajax';
  33. /**
  34. * Sets the AJAX parameter from the current request.
  35. *
  36. * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
  37. * The response event, which contains the current request.
  38. */
  39. public function onRequest(GetResponseEvent $event) {
  40. // Pass to the Html class that the current request is an Ajax request.
  41. if ($event->getRequest()->request->get(static::AJAX_REQUEST_PARAMETER)) {
  42. Html::setIsAjax(TRUE);
  43. }
  44. }
  45. /**
  46. * Renders the ajax commands right before preparing the result.
  47. *
  48. * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  49. * The response event, which contains the possible AjaxResponse object.
  50. */
  51. public function onResponse(FilterResponseEvent $event) {
  52. $response = $event->getResponse();
  53. if ($response instanceof AjaxResponse) {
  54. $this->ajaxResponseAttachmentsProcessor->processAttachments($response);
  55. // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
  56. // for that browser, jquery.form submits requests containing a file upload
  57. // via an IFRAME rather than via XHR. Since the response is being sent to
  58. // an IFRAME, it must be formatted as HTML. Specifically:
  59. // - It must use the text/html content type or else the browser will
  60. // present a download prompt. Note: This applies to both file uploads
  61. // as well as any ajax request in a form with a file upload form.
  62. // - It must place the JSON data into a textarea to prevent browser
  63. // extensions such as Linkification and Skype's Browser Highlighter
  64. // from applying HTML transformations such as URL or phone number to
  65. // link conversions on the data values.
  66. //
  67. // Since this affects the format of the output, it could be argued that
  68. // this should be implemented as a separate Accept MIME type. However,
  69. // that would require separate variants for each type of AJAX request
  70. // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
  71. // this browser workaround is implemented via a GET or POST parameter.
  72. //
  73. // @see http://malsup.com/jquery/form/#file-upload
  74. // @see https://www.drupal.org/node/1009382
  75. // @see https://www.drupal.org/node/2339491
  76. // @see Drupal.ajax.prototype.beforeSend()
  77. $accept = $event->getRequest()->headers->get('accept');
  78. if (strpos($accept, 'text/html') !== FALSE) {
  79. $response->headers->set('Content-Type', 'text/html; charset=utf-8');
  80. // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
  81. // and Skype's Browser Highlighter, convert URLs, phone numbers, etc.
  82. // into links. This corrupts the JSON response. Protect the integrity of
  83. // the JSON data by making it the value of a textarea.
  84. // @see http://malsup.com/jquery/form/#file-upload
  85. // @see https://www.drupal.org/node/1009382
  86. $response->setContent('<textarea>' . $response->getContent() . '</textarea>');
  87. }
  88. // User-uploaded files cannot set any response headers, so a custom header
  89. // is used to indicate to ajax.js that this response is safe. Note that
  90. // most Ajax requests bound using the Form API will be protected by having
  91. // the URL flagged as trusted in Drupal.settings, so this header is used
  92. // only for things like custom markup that gets Ajax behaviors attached.
  93. $response->headers->set('X-Drupal-Ajax-Token', 1);
  94. }
  95. }
  96. /**
  97. * {@inheritdoc}
  98. */
  99. public static function getSubscribedEvents() {
  100. $events[KernelEvents::RESPONSE][] = ['onResponse', -100];
  101. $events[KernelEvents::REQUEST][] = ['onRequest', 50];
  102. return $events;
  103. }
  104. }