FormAjaxSubscriber.php 5.8 KB

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