3 namespace Drupal\Core\Form\EventSubscriber;
5 use Drupal\Core\Ajax\AjaxResponse;
6 use Drupal\Core\Ajax\PrependCommand;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Form\Exception\BrokenPostRequestException;
9 use Drupal\Core\Form\FormAjaxException;
10 use Drupal\Core\Form\FormAjaxResponseBuilderInterface;
11 use Drupal\Core\Form\FormBuilderInterface;
12 use Drupal\Core\StringTranslation\StringTranslationTrait;
13 use Drupal\Core\StringTranslation\TranslationInterface;
14 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15 use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
16 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
17 use Symfony\Component\HttpKernel\KernelEvents;
20 * Wraps AJAX form submissions that are triggered via an exception.
22 class FormAjaxSubscriber implements EventSubscriberInterface {
24 use StringTranslationTrait;
27 * The form AJAX response builder.
29 * @var \Drupal\Core\Form\FormAjaxResponseBuilderInterface
31 protected $formAjaxResponseBuilder;
34 * Constructs a new FormAjaxSubscriber.
36 * @param \Drupal\Core\Form\FormAjaxResponseBuilderInterface $form_ajax_response_builder
37 * The form AJAX response builder.
38 * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
39 * The string translation.
41 public function __construct(FormAjaxResponseBuilderInterface $form_ajax_response_builder, TranslationInterface $string_translation) {
42 $this->formAjaxResponseBuilder = $form_ajax_response_builder;
43 $this->stringTranslation = $string_translation;
47 * Alters the wrapper format if this is an AJAX form request.
49 * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
50 * The event to process.
52 public function onView(GetResponseForControllerResultEvent $event) {
53 // To support an AJAX form submission of a form within a block, make the
54 // later VIEW subscribers process the controller result as though for
55 // HTML display (i.e., add blocks). During that block building, when the
56 // submitted form gets processed, an exception gets thrown by
57 // \Drupal\Core\Form\FormBuilderInterface::buildForm(), allowing
58 // self::onException() to return an AJAX response instead of an HTML one.
59 $request = $event->getRequest();
60 if ($request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
61 $request->query->set(MainContentViewSubscriber::WRAPPER_FORMAT, 'html');
66 * Catches a form AJAX exception and build a response from it.
68 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
69 * The event to process.
71 public function onException(GetResponseForExceptionEvent $event) {
72 $exception = $event->getException();
73 $request = $event->getRequest();
75 // Render a nice error message in case we have a file upload which exceeds
76 // the configured upload limit.
77 if ($exception instanceof BrokenPostRequestException && $request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
78 $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');
79 $response = new AjaxResponse(NULL, 200);
80 $status_messages = ['#type' => 'status_messages'];
81 $response->addCommand(new PrependCommand(NULL, $status_messages));
82 $event->allowCustomResponseCode();
83 $event->setResponse($response);
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();
94 // Set the build ID from the request as the old build ID on the form.
95 $form['#build_id_old'] = $request->request->get('form_build_id');
98 $response = $this->formAjaxResponseBuilder->buildResponse($request, $form, $form_state, []);
100 // Since this response is being set in place of an exception, explicitly
101 // mark this as a 200 status.
102 $response->setStatusCode(200);
103 $event->allowCustomResponseCode();
104 $event->setResponse($response);
106 catch (\Exception $e) {
107 // Otherwise, replace the existing exception with the new one.
108 $event->setException($e);
114 * Extracts a form AJAX exception.
116 * @param \Exception $e
117 * A generic exception that might contain a form AJAX exception.
119 * @return \Drupal\Core\Form\FormAjaxException|null
120 * Either the form AJAX exception, or NULL if none could be found.
122 protected function getFormAjaxException(\Exception $e) {
125 if ($e instanceof FormAjaxException) {
130 $e = $e->getPrevious();
136 * Wraps format_size()
139 * The formatted size.
141 protected function formatSize($size) {
142 return format_size($size);
148 public static function getSubscribedEvents() {
149 // Run before exception.logger.
150 $events[KernelEvents::EXCEPTION] = ['onException', 51];
151 // Run before main_content_view_subscriber.
152 $events[KernelEvents::VIEW][] = ['onView', 1];
158 * Wraps drupal_set_message().
160 * @codeCoverageIgnore
162 protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
163 drupal_set_message($message, $type, $repeat);