You are here

class AutosaveFormBuilder in Autosave Form 8

Provides form building and processing with AutosaveForm enabled.

Hierarchy

Expanded class hierarchy of AutosaveFormBuilder

1 string reference to 'AutosaveFormBuilder'
autosave_form.services.yml in ./autosave_form.services.yml
autosave_form.services.yml
1 service uses AutosaveFormBuilder
form_builder.autosave_form in ./autosave_form.services.yml
\Drupal\autosave_form\Form\AutosaveFormBuilder

File

src/Form/AutosaveFormBuilder.php, line 30

Namespace

Drupal\autosave_form\Form
View source
class AutosaveFormBuilder extends FormBuilder {
  use AutosaveButtonClickedTrait;

  /**
   * The form builder service.
   *
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected $formBuilder;

  /**
   * The autosave form storage.
   *
   * @var \Drupal\autosave_form\Storage\AutosaveEntityFormStorageInterface
   */
  protected $autosaveEntityFormStorage;

  /**
   * Controls whether to execute ::doBuildForm or not.
   *
   * If set to FALSE the normal form processing will run, otherwise if set to
   * TRUE doBuildForm will not be executed. This is useful in the use case
   * where we don't need the processed form like in e.g. autosave submit after
   * having already at least one autosave state, from which point we don't need
   * the form state values, but only the user input and the last cached form
   * state.
   *
   * @var bool
   */
  protected $doBuildFormSkip = FALSE;

  /**
   * Constructs a new FormBuilder.
   *
   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
   *   The form builder service.
   * @param \Drupal\Core\Form\FormValidatorInterface $form_validator
   *   The form validator.
   * @param \Drupal\Core\Form\FormSubmitterInterface $form_submitter
   *   The form submission processor.
   * @param \Drupal\Core\Form\FormCacheInterface $form_cache
   *   The form cache.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
   *   The class resolver.
   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
   *   The element info manager.
   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
   *   The theme manager.
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
   *   The CSRF token generator.
   * @param \Drupal\autosave_form\Storage\AutosaveEntityFormStorageInterface $autosave_entity_form_storage
   *   The autosave form storage service.
   */
  public function __construct(FormBuilderInterface $form_builder, FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, AutosaveEntityFormStorageInterface $autosave_entity_form_storage) {
    parent::__construct($form_validator, $form_submitter, $form_cache, $module_handler, $event_dispatcher, $request_stack, $class_resolver, $element_info, $theme_manager, $csrf_token);
    $this->formBuilder = $form_builder;
    $this->autosaveEntityFormStorage = $autosave_entity_form_storage;
  }
  public function buildForm($form_id, FormStateInterface &$form_state) {
    $form = parent::buildForm($form_id, $form_state);
    if ($form_state::hasAnyErrors()) {

      // Under circumstances it might happen that the form is submitted but
      // returned with validation errors and the form alter hooks are executed
      // thus leading to the autosave form alter code being executed as well and
      // putting the autosave resume/discard message to the form, which should
      // not happen if the form is being returned to the browser with validation
      // errors. In order to prevent this we have to add the resume/discard
      // message and options only on HTTP GET requests or on POST requests if
      // restore or reject submit operations have been performed or in a more
      // complex case if the message has not been yet confirmed but other
      // AJAX / POST requests are being triggered in the background. As we could
      // not detect the last case we still put the form elements into the form,
      // but on the client side we will not show the message if the form is
      // returned with validation errors.
      $form['#attached']['drupalSettings']['autosaveForm']['formHasErrors'] = TRUE;

      // Additionally unset the form elements and settings which might have been
      // added, but aren't actually needed.
      unset($form['#attached']['drupalSettings']['autosaveForm']['message']);
      unset($form[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME]);
      unset($form[AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME]);
      unset($form['autosave_restore_discard']);
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function processForm($form_id, &$form, FormStateInterface &$form_state) {
    if ($this
      ->isAutosaveTriggered($form_state)) {

      // @todo should we add a condition, that the form state is already cached
      // in order to stop fully processing the form?
      $this->doBuildFormSkip = TRUE;

      // As we'll skip doBuildForm we have to take care of setting the
      // triggering element.
      $form_state
        ->setTriggeringElement($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]);

      // Needed to execute the submit handler, as this will not be done if
      // duBuildForm is not being executed.
      $form_state
        ->setSubmitHandlers($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]['#submit']);
      $form_state
        ->setProcessInput();
      $form_state
        ->setSubmitted();
    }
    $response = parent::processForm($form_id, $form, $form_state);
    $this->doBuildFormSkip = FALSE;
    return $response;
  }

  /**
   * {@inheritdoc}
   */
  public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) {
    return $this->doBuildFormSkip ? $element : parent::doBuildForm($form_id, $element, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) {
    $this
      ->restoreAutosavedState($form_id, $form_state);
    return parent::rebuildForm($form_id, $form_state, $old_form);
  }

  /**
   * Restores an autosaved form state.
   *
   * @param $form_id
   *   The form id.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  protected function restoreAutosavedState($form_id, FormStateInterface $form_state) {
    if (!$form_state
      ->get('autosave_form_restored') && ($autosaved_timestamp = $form_state
      ->get('autosave_form_state_timestamp'))) {
      $form_object = $form_state
        ->getFormObject();

      // Restore entity form.
      if ($form_object instanceof EntityFormInterface) {
        $entity = $form_object
          ->getEntity();
        $autosaved_state = $this->autosaveEntityFormStorage
          ->getEntityAndFormState($form_id, $entity
          ->getEntityTypeId(), $entity
          ->id(), $entity
          ->language()
          ->getId(), $this
          ->currentUser()
          ->id(), NULL, $autosaved_timestamp);
        if (is_null($autosaved_state)) {

          // @todo Cover the case that the autosaved state has been purged
          // meanwhile.
          return;
        }

        /** @var EntityInterface $autosaved_entity */
        $autosaved_entity = $autosaved_state['entity'];

        /** @var FormStateInterface $autosaved_form_state */
        $autosaved_form_state = $autosaved_state['form_state'];

        // Restore the form with the entity from the autosaved state.
        $form_object
          ->setEntity($autosaved_entity);

        // Restore the user input.
        $current_user_input = $form_state
          ->getUserInput();
        $autosaved_user_input = $autosaved_form_state
          ->getUserInput();

        // We have to rebuild the form and keep the generated form token
        // instead of putting the one from the autosaved input, otherwise the
        // form builder will set an form state error and, which is going to
        // result into an exception, as setting form state errors after the
        // validation phase is forbidden.
        if (isset($current_user_input['form_token'])) {
          $autosaved_user_input['form_token'] = $current_user_input['form_token'];
        }
        $form_state
          ->setUserInput($autosaved_user_input);

        // Recover the form state storage, which is needed to continue from the
        // state at which the form was left.
        $form_state
          ->setStorage($autosaved_form_state
          ->getStorage());

        // Flag the form state as being restored from autosave.
        $form_state
          ->set('autosave_form_restored', TRUE);
      }
      elseif ($form_object instanceof FormInterface) {

        // @todo add support for regular forms.
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
    $prevent_hooks = FALSE;
    if ($this
      ->isAutosaveTriggered($form_state)) {

      // There is no need of generating a new form build id after triggering
      // autosave.
      $form['#build_id'] = $form_state
        ->getUserInput()['form_build_id'];
      if ($form_state
        ->isCached()) {
        $prevent_hooks = TRUE;
      }
    }
    if ($prevent_hooks) {

      // Prevent running hooks.
      $module_handler = $this->moduleHandler;
      $theme_manager = $this->themeManager;
      $this->moduleHandler = new ModuleHandlerEmptyAlter();
      $this->themeManager = new ThemeManagerEmptyAlter();
    }
    parent::prepareForm($form_id, $form, $form_state);
    if ($prevent_hooks) {
      $this->moduleHandler = $module_handler;
      $this->themeManager = $theme_manager;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AutosaveButtonClickedTrait::isAutosaveTriggered protected function Checks if the submission is triggered by autosave save.
AutosaveButtonClickedTrait::isRejectTriggered protected function Checks if autosave restore has been triggered.
AutosaveButtonClickedTrait::isRestoreTriggered protected function Checks if autosave restore has been triggered.
AutosaveFormBuilder::$autosaveEntityFormStorage protected property The autosave form storage.
AutosaveFormBuilder::$doBuildFormSkip protected property Controls whether to execute ::doBuildForm or not.
AutosaveFormBuilder::$formBuilder protected property The form builder service.
AutosaveFormBuilder::buildForm public function Builds and processes a form for a given form ID. Overrides FormBuilder::buildForm
AutosaveFormBuilder::doBuildForm public function Builds and processes all elements in the structured form array. Overrides FormBuilder::doBuildForm
AutosaveFormBuilder::prepareForm public function Prepares a structured form array. Overrides FormBuilder::prepareForm
AutosaveFormBuilder::processForm public function Processes a form submission. Overrides FormBuilder::processForm
AutosaveFormBuilder::rebuildForm public function Constructs a new $form from the information in $form_state. Overrides FormBuilder::rebuildForm
AutosaveFormBuilder::restoreAutosavedState protected function Restores an autosaved form state.
AutosaveFormBuilder::__construct public function Constructs a new FormBuilder. Overrides FormBuilder::__construct
FormBuilder::$classResolver protected property The class resolver.
FormBuilder::$csrfToken protected property The CSRF token generator to validate the form token.
FormBuilder::$currentUser protected property The current user.
FormBuilder::$elementInfo protected property The element info manager.
FormBuilder::$eventDispatcher protected property The event dispatcher.
FormBuilder::$formCache protected property The form cache.
FormBuilder::$formSubmitter protected property The form submitter.
FormBuilder::$formValidator protected property The form validator.
FormBuilder::$moduleHandler protected property The module handler.
FormBuilder::$requestStack protected property The request stack.
FormBuilder::$safeCoreValueCallables protected property Defines element value callables which are safe to run even when the form state has an invalid CSRF token.
FormBuilder::$themeManager protected property The theme manager.
FormBuilder::buildFormAction protected function Builds the $form['#action'].
FormBuilder::buttonWasClicked protected function Determines if a given button triggered the form submission.
FormBuilder::currentUser protected function Gets the current active user.
FormBuilder::deleteCache public function Deletes a form in the cache. Overrides FormCacheInterface::deleteCache
FormBuilder::doSubmitForm public function Handles the submitted form, executing callbacks and processing responses. Overrides FormSubmitterInterface::doSubmitForm
FormBuilder::elementTriggeredScriptedSubmission protected function Detects if an element triggered the form submission via Ajax.
FormBuilder::executeSubmitHandlers public function Executes custom submission handlers for a given form. Overrides FormSubmitterInterface::executeSubmitHandlers
FormBuilder::executeValidateHandlers public function Executes custom validation handlers for a given form. Overrides FormValidatorInterface::executeValidateHandlers
FormBuilder::getCache public function Fetches a form from the cache. Overrides FormCacheInterface::getCache
FormBuilder::getFileUploadMaxSize protected function Wraps file_upload_max_size().
FormBuilder::getForm public function Gets a renderable form array. Overrides FormBuilderInterface::getForm
FormBuilder::getFormId public function Determines the ID of a form. Overrides FormBuilderInterface::getFormId
FormBuilder::handleInputElement protected function Adds the #name and #value properties of an input element before rendering.
FormBuilder::redirectForm public function Redirects the user to a URL after a form has been processed. Overrides FormSubmitterInterface::redirectForm
FormBuilder::renderFormTokenPlaceholder public function Renders the form CSRF token. It's a #lazy_builder callback.
FormBuilder::renderPlaceholderFormAction public function Renders a form action URL. It's a #lazy_builder callback.
FormBuilder::retrieveForm public function Retrieves the structured array that defines a given form. Overrides FormBuilderInterface::retrieveForm
FormBuilder::setCache public function Stores a form in the cache. Overrides FormCacheInterface::setCache
FormBuilder::setInvalidTokenError public function Sets a form_token error on the given form state. Overrides FormValidatorInterface::setInvalidTokenError
FormBuilder::submitForm public function Retrieves, populates, and processes a form. Overrides FormBuilderInterface::submitForm
FormBuilder::trustedCallbacks public static function Lists the trusted callbacks provided by the implementing class. Overrides TrustedCallbackInterface::trustedCallbacks
FormBuilder::validateForm public function Validates user-submitted form data in the $form_state. Overrides FormValidatorInterface::validateForm
FormBuilder::valueCallableIsSafe protected function Helper function to normalize the different callable formats.
FormBuilderInterface::AJAX_FORM_REQUEST constant Request key for AJAX forms that submit to the form's original route.
TrustedCallbackInterface::THROW_EXCEPTION constant Untrusted callbacks throw exceptions.
TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION constant Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
TrustedCallbackInterface::TRIGGER_WARNING constant Untrusted callbacks trigger E_USER_WARNING errors.