Same name and namespace in other branches
  1. 8.9.x core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php \Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber
  2. 9 core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php \Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber

Wraps AJAX form submissions that are triggered via an exception.

Hierarchy

Expanded class hierarchy of FormAjaxSubscriber

1 file declares its use of FormAjaxSubscriber
FormAjaxSubscriberTest.php in core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
1 string reference to 'FormAjaxSubscriber'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses FormAjaxSubscriber
form_ajax_subscriber in core/core.services.yml
Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber

File

core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php, line 24

Namespace

Drupal\Core\Form\EventSubscriber
View source
class FormAjaxSubscriber implements EventSubscriberInterface {
  use StringTranslationTrait;

  /**
   * The form AJAX response builder.
   *
   * @var \Drupal\Core\Form\FormAjaxResponseBuilderInterface
   */
  protected $formAjaxResponseBuilder;

  /**
   * The messenger.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Constructs a new FormAjaxSubscriber.
   *
   * @param \Drupal\Core\Form\FormAjaxResponseBuilderInterface $form_ajax_response_builder
   *   The form AJAX response builder.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   The string translation.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger.
   */
  public function __construct(FormAjaxResponseBuilderInterface $form_ajax_response_builder, TranslationInterface $string_translation, MessengerInterface $messenger) {
    $this->formAjaxResponseBuilder = $form_ajax_response_builder;
    $this->stringTranslation = $string_translation;
    $this->messenger = $messenger;
  }

  /**
   * Alters the wrapper format if this is an AJAX form request.
   *
   * @param \Symfony\Component\HttpKernel\Event\ViewEvent $event
   *   The event to process.
   */
  public function onView(ViewEvent $event) {

    // To support an AJAX form submission of a form within a block, make the
    // later VIEW subscribers process the controller result as though for
    // HTML display (i.e., add blocks). During that block building, when the
    // submitted form gets processed, an exception gets thrown by
    // \Drupal\Core\Form\FormBuilderInterface::buildForm(), allowing
    // self::onException() to return an AJAX response instead of an HTML one.
    $request = $event
      ->getRequest();
    if ($request->query
      ->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
      $request->query
        ->set(MainContentViewSubscriber::WRAPPER_FORMAT, 'html');
    }
  }

  /**
   * Catches a form AJAX exception and build a response from it.
   *
   * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
   *   The event to process.
   */
  public function onException(ExceptionEvent $event) {
    $exception = $event
      ->getThrowable();
    $request = $event
      ->getRequest();

    // Render a nice error message in case we have a file upload which exceeds
    // the configured upload limit.
    if ($exception instanceof BrokenPostRequestException && $request->query
      ->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
      $this->messenger
        ->addError($this
        ->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', [
        '@size' => ByteSizeMarkup::create((int) $exception
          ->getSize()),
      ]));
      $response = new AjaxResponse(NULL, 200);
      $status_messages = [
        '#type' => 'status_messages',
      ];
      $response
        ->addCommand(new PrependCommand(NULL, $status_messages));
      $event
        ->allowCustomResponseCode();
      $event
        ->setResponse($response);
      return;
    }

    // Extract the form AJAX exception (it may have been passed to another
    // exception before reaching here).
    if ($exception = $this
      ->getFormAjaxException($exception)) {
      $request = $event
        ->getRequest();
      $form = $exception
        ->getForm();
      $form_state = $exception
        ->getFormState();

      // Set the build ID from the request as the old build ID on the form.
      $form['#build_id_old'] = $request->request
        ->get('form_build_id');
      try {
        $response = $this->formAjaxResponseBuilder
          ->buildResponse($request, $form, $form_state, []);

        // Since this response is being set in place of an exception, explicitly
        // mark this as a 200 status.
        $response
          ->setStatusCode(200);
        $event
          ->allowCustomResponseCode();
        $event
          ->setResponse($response);
      } catch (\Exception $e) {

        // Otherwise, replace the existing exception with the new one.
        $event
          ->setThrowable($e);
      }
    }
  }

  /**
   * Extracts a form AJAX exception.
   *
   * @param \Throwable $e
   *   A generic exception that might contain a form AJAX exception.
   *
   * @return \Drupal\Core\Form\FormAjaxException|null
   *   Either the form AJAX exception, or NULL if none could be found.
   */
  protected function getFormAjaxException(\Throwable $e) {
    $exception = NULL;
    while ($e) {
      if ($e instanceof FormAjaxException) {
        $exception = $e;
        break;
      }
      $e = $e
        ->getPrevious();
    }
    return $exception;
  }

  /**
   * Wraps format_size()
   *
   * @return string
   *   The formatted size.
   *
   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
   *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create() instead.
   *
   * @see https://www.drupal.org/node/2999981
   */
  protected function formatSize($size) {
    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \\Drupal\\Core\\StringTranslation\\ByteSizeMarkup::create() instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
    return format_size($size);
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {

    // Run before exception.logger.
    $events[KernelEvents::EXCEPTION] = [
      'onException',
      51,
    ];

    // Run before main_content_view_subscriber.
    $events[KernelEvents::VIEW][] = [
      'onView',
      1,
    ];
    return $events;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
FormAjaxSubscriber::$formAjaxResponseBuilder protected property The form AJAX response builder.
FormAjaxSubscriber::$messenger protected property The messenger.
FormAjaxSubscriber::formatSize Deprecated protected function Wraps format_size()
FormAjaxSubscriber::getFormAjaxException protected function Extracts a form AJAX exception.
FormAjaxSubscriber::getSubscribedEvents public static function
FormAjaxSubscriber::onException public function Catches a form AJAX exception and build a response from it.
FormAjaxSubscriber::onView public function Alters the wrapper format if this is an AJAX form request.
FormAjaxSubscriber::__construct public function Constructs a new FormAjaxSubscriber.
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 1
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.