FormAjaxSubscriber.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. namespace Drupal\Core\Form\EventSubscriber;
  3. use Drupal\Core\Ajax\AjaxResponse;
  4. use Drupal\Core\Ajax\PrependCommand;
  5. use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
  6. use Drupal\Core\Form\Exception\BrokenPostRequestException;
  7. use Drupal\Core\Form\FormAjaxException;
  8. use Drupal\Core\Form\FormAjaxResponseBuilderInterface;
  9. use Drupal\Core\Form\FormBuilderInterface;
  10. use Drupal\Core\StringTranslation\StringTranslationTrait;
  11. use Drupal\Core\StringTranslation\TranslationInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
  14. use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
  15. use Symfony\Component\HttpKernel\KernelEvents;
  16. /**
  17. * Wraps AJAX form submissions that are triggered via an exception.
  18. */
  19. class FormAjaxSubscriber implements EventSubscriberInterface {
  20. use StringTranslationTrait;
  21. /**
  22. * The form AJAX response builder.
  23. *
  24. * @var \Drupal\Core\Form\FormAjaxResponseBuilderInterface
  25. */
  26. protected $formAjaxResponseBuilder;
  27. /**
  28. * Constructs a new FormAjaxSubscriber.
  29. *
  30. * @param \Drupal\Core\Form\FormAjaxResponseBuilderInterface $form_ajax_response_builder
  31. * The form AJAX response builder.
  32. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
  33. * The string translation.
  34. */
  35. public function __construct(FormAjaxResponseBuilderInterface $form_ajax_response_builder, TranslationInterface $string_translation) {
  36. $this->formAjaxResponseBuilder = $form_ajax_response_builder;
  37. $this->stringTranslation = $string_translation;
  38. }
  39. /**
  40. * Alters the wrapper format if this is an AJAX form request.
  41. *
  42. * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
  43. * The event to process.
  44. */
  45. public function onView(GetResponseForControllerResultEvent $event) {
  46. // To support an AJAX form submission of a form within a block, make the
  47. // later VIEW subscribers process the controller result as though for
  48. // HTML display (i.e., add blocks). During that block building, when the
  49. // submitted form gets processed, an exception gets thrown by
  50. // \Drupal\Core\Form\FormBuilderInterface::buildForm(), allowing
  51. // self::onException() to return an AJAX response instead of an HTML one.
  52. $request = $event->getRequest();
  53. if ($request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
  54. $request->query->set(MainContentViewSubscriber::WRAPPER_FORMAT, 'html');
  55. }
  56. }
  57. /**
  58. * Catches a form AJAX exception and build a response from it.
  59. *
  60. * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  61. * The event to process.
  62. */
  63. public function onException(GetResponseForExceptionEvent $event) {
  64. $exception = $event->getException();
  65. $request = $event->getRequest();
  66. // Render a nice error message in case we have a file upload which exceeds
  67. // the configured upload limit.
  68. if ($exception instanceof BrokenPostRequestException && $request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
  69. $this->drupalSetMessage($this->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', ['@size' => $this->formatSize($exception->getSize())]), 'error');
  70. $response = new AjaxResponse(NULL, 200);
  71. $status_messages = ['#type' => 'status_messages'];
  72. $response->addCommand(new PrependCommand(NULL, $status_messages));
  73. $event->allowCustomResponseCode();
  74. $event->setResponse($response);
  75. return;
  76. }
  77. // Extract the form AJAX exception (it may have been passed to another
  78. // exception before reaching here).
  79. if ($exception = $this->getFormAjaxException($exception)) {
  80. $request = $event->getRequest();
  81. $form = $exception->getForm();
  82. $form_state = $exception->getFormState();
  83. // Set the build ID from the request as the old build ID on the form.
  84. $form['#build_id_old'] = $request->request->get('form_build_id');
  85. try {
  86. $response = $this->formAjaxResponseBuilder->buildResponse($request, $form, $form_state, []);
  87. // Since this response is being set in place of an exception, explicitly
  88. // mark this as a 200 status.
  89. $response->setStatusCode(200);
  90. $event->allowCustomResponseCode();
  91. $event->setResponse($response);
  92. }
  93. catch (\Exception $e) {
  94. // Otherwise, replace the existing exception with the new one.
  95. $event->setException($e);
  96. }
  97. }
  98. }
  99. /**
  100. * Extracts a form AJAX exception.
  101. *
  102. * @param \Exception $e
  103. * A generic exception that might contain a form AJAX exception.
  104. *
  105. * @return \Drupal\Core\Form\FormAjaxException|null
  106. * Either the form AJAX exception, or NULL if none could be found.
  107. */
  108. protected function getFormAjaxException(\Exception $e) {
  109. $exception = NULL;
  110. while ($e) {
  111. if ($e instanceof FormAjaxException) {
  112. $exception = $e;
  113. break;
  114. }
  115. $e = $e->getPrevious();
  116. }
  117. return $exception;
  118. }
  119. /**
  120. * Wraps format_size()
  121. *
  122. * @return string
  123. * The formatted size.
  124. */
  125. protected function formatSize($size) {
  126. return format_size($size);
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public static function getSubscribedEvents() {
  132. // Run before exception.logger.
  133. $events[KernelEvents::EXCEPTION] = ['onException', 51];
  134. // Run before main_content_view_subscriber.
  135. $events[KernelEvents::VIEW][] = ['onView', 1];
  136. return $events;
  137. }
  138. /**
  139. * Wraps drupal_set_message().
  140. *
  141. * @codeCoverageIgnore
  142. */
  143. protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
  144. drupal_set_message($message, $type, $repeat);
  145. }
  146. }