Skip to content
FormState.php 30 KiB
Newer Older
<?php

/**
 * @file
 * Contains \Drupal\Core\Form\FormState.
 */

namespace Drupal\Core\Form;

use Drupal\Component\Utility\NestedArray;
use Symfony\Component\HttpFoundation\Response;
class FormState implements FormStateInterface {

  /**
   * Tracks if any errors have been set on any form.
   *
   * @var bool
   */
  protected static $anyErrors = FALSE;

  /**
   * The complete form structure.
   *
   * #process, #after_build, #element_validate, and other handlers being invoked
   * on a form element may use this reference to access other information in the
   * form the element is contained in.
   *
   * @see self::getCompleteForm()
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $complete_form;

  /**
   * An associative array of information stored by Form API that is necessary to
   * build and rebuild the form from cache when the original context may no
   * longer be available:
   *   - callback: The actual callback to be used to retrieve the form array.
   *     Can be any callable. If none is provided $form_id is used as the name
   *     of a function to call instead.
   *   - args: A list of arguments to pass to the form constructor.
   *   - files: An optional array defining include files that need to be loaded
   *     for building the form. Each array entry may be the path to a file or
   *     another array containing values for the parameters 'type', 'module' and
   *     'name' as needed by module_load_include(). The files listed here are
   *     automatically loaded by form_get_cache(). By default the current menu
   *     router item's 'file' definition is added, if any. Use
   *     self::loadInclude() to add include files from a form constructor.
   *   - form_id: Identification of the primary form being constructed and
   *     processed.
   *   - base_form_id: Identification for a base form, as declared in the form
   *     class's \Drupal\Core\Form\BaseFormIdInterface::getBaseFormId() method.
   *   - immutable: If this flag is set to TRUE, a new form build id is
   *     generated when the form is loaded from the cache. If it is subsequently
   *     saved to the cache again, it will have another cache id and therefore
   *     the original form and form-state will remain unaltered. This is
   *     important when page caching is enabled in order to prevent form state
   *     from leaking between anonymous users.
   *
   * @var array
   */
  protected $build_info = array(
    'args' => array(),
    'files' => array(),
  );

  /**
   * Similar to self::$build_info, but pertaining to
   * \Drupal\Core\Form\FormBuilderInterface::rebuildForm().
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $rebuild_info = array();

  /**
   * Normally, after the entire form processing is completed and submit handlers
   * have run, a form is considered to be done and
   * \Drupal\Core\Form\FormSubmitterInterface::redirectForm() will redirect the
   * user to a new page using a GET request (so a browser refresh does not
   * re-submit the form). However, if 'rebuild' has been set to TRUE, then a new
   * copy of the form is immediately built and sent to the browser, instead of a
   * redirect. This is used for multi-step forms, such as wizards and
   * confirmation forms. Normally, self::$rebuild is set by a submit handler,
   * since its is usually logic within a submit handler that determines whether
   * a form is done or requires another step. However, a validation handler may
   * already set self::$rebuild to cause the form processing to bypass submit
   * handlers and rebuild the form instead, even if there are no validation
   * errors.
   *
   * This property is uncacheable.
   *
   * @see self::setRebuild()
   *
   * @var bool
   */
  protected $rebuild = FALSE;

  /**
   * Used when a form needs to return some kind of a
   * \Symfony\Component\HttpFoundation\Response object, e.g., a
   * \Symfony\Component\HttpFoundation\BinaryFileResponse when triggering a
   * file download. If you use self::setRedirect() or self::setRedirectUrl(),
   * it will be used to build a
   * \Symfony\Component\HttpFoundation\RedirectResponse and will populate this
   * key.
   *
   * @var \Symfony\Component\HttpFoundation\Response|null
   */
  protected $response;

  /**
   * Used to redirect the form on submission.
   * @var \Drupal\Core\Url|\Symfony\Component\HttpFoundation\RedirectResponse|null

  /**
   * If set to TRUE the form will NOT perform a redirect, even if
   * self::$redirect is set.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $no_redirect;

  /**
   * The HTTP form method to use for finding the input for this form.
   *
   * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method forms do
   * not use form ids so are always considered to be submitted, which can have
   * unexpected effects. The 'get' method should only be used on forms that do
   * not change data, as that is exclusively the domain of 'post.'
   *
   * This property is uncacheable.
   *
   * @var string
   */
  protected $method = 'post';

  /**
   * If set to TRUE the original, unprocessed form structure will be cached,
   * which allows the entire form to be rebuilt from cache. A typical form
   * workflow involves two page requests; first, a form is built and rendered
   * for the user to fill in. Then, the user fills the form in and submits it,
   * triggering a second page request in which the form must be built and
   * processed. By default, $form and $form_state are built from scratch during
   * each of these page requests. Often, it is necessary or desired to persist
   * the $form and $form_state variables from the initial page request to the
   * one that processes the submission. 'cache' can be set to TRUE to do this.
   * A prominent example is an Ajax-enabled form, in which
   * \Drupal\Core\Render\Element\RenderElement::processAjaxForm()
   * enables form caching for all forms that include an element with the #ajax
   * property. (The Ajax handler has no way to build the form itself, so must
   * rely on the cached version.) Note that the persistence of $form and
   * $form_state happens automatically for (multi-step) forms having the
   * self::$rebuild flag set, regardless of the value for self::$cache.
   *
   * @var bool
   */
  protected $cache = FALSE;

  /**
   * If set to TRUE the form will NOT be cached, even if 'cache' is set.
   *
   * @var bool
   */
  protected $no_cache;

  /**
   * An associative array of values submitted to the form.
   *
   * The validation functions and submit functions use this array for nearly all
   * their decision making. (Note that #tree determines whether the values are a
   * flat array or an array whose structure parallels the $form array. See the
   * @link forms_api_reference.html Form API reference @endlink for more
   * information.)
   *
   * This property is uncacheable.
   *
   * @var array
   */

  /**
   * The array of values as they were submitted by the user.
   *
   * These are raw and unvalidated, so should not be used without a thorough
   * understanding of security implications. In almost all cases, code should
   * use the data in the 'values' array exclusively. The most common use of this
   * key is for multi-step forms that need to clear some of the user input when
   * setting 'rebuild'. The values correspond to \Drupal::request()->request or
   * \Drupal::request()->query, depending on the 'method' chosen.
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $input;

  /**
   * If TRUE and the method is GET, a form_id is not necessary.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $always_process;

  /**
   * Ordinarily, a form is only validated once, but there are times when a form
   * is resubmitted internally and should be validated again. Setting this to
   * TRUE will force that to happen. This is most likely to occur during Ajax
   * operations.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $must_validate;

  /**
   * If TRUE, the form was submitted programmatically, usually invoked via
   * \Drupal\Core\Form\FormBuilderInterface::submitForm(). Defaults to FALSE.
   *
   * @var bool
   */
  protected $programmed = FALSE;

  /**
   * If TRUE, programmatic form submissions are processed without taking #access
   * into account. Set this to FALSE when submitting a form programmatically
   * with values that may have been input by the user executing the current
   * request; this will cause #access to be respected as it would on a normal
   * form submission. Defaults to TRUE.
   *
   * @var bool
   */
  protected $programmed_bypass_access_check = TRUE;

  /**
   * TRUE signifies correct form submission. This is always TRUE for programmed
   * forms coming from \Drupal\Core\Form\FormBuilderInterface::submitForm() (see
   * 'programmed' key), or if the form_id coming from the
   * \Drupal::request()->request data is set and matches the current form_id.
   *
   * @var bool
   */
  protected $process_input;

  /**
   * If TRUE, the form has been submitted. Defaults to FALSE.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $submitted = FALSE;

  /**
   * If TRUE, the form was submitted and has been processed and executed.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $executed = FALSE;

  /**
   * The form element that triggered submission, which may or may not be a
   * button (in the case of Ajax forms). This key is often used to distinguish
   * between various buttons in a submit handler, and is also used in Ajax
   * handlers.
   *
   * This property is uncacheable.
   *
   * @var array|null
   */
  protected $triggering_element;

  /**
   * If TRUE, there is a file element and Form API will set the appropriate
   * 'enctype' HTML attribute on the form.
   *
   * @var bool
   */
  protected $has_file_element;

  /**
   * Contains references to details elements to render them within vertical tabs.
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $groups = array();

  /**
   *  This is not a special key, and no specific support is provided for it in
   *  the Form API. By tradition it was the location where application-specific
   *  data was stored for communication between the submit, validation, and form
   *  builder functions, especially in a multi-step-style form. Form
   *  implementations may use any key(s) within $form_state (other than the keys
   *  listed here and other reserved ones used by Form API internals) for this
   *  kind of storage. The recommended way to ensure that the chosen key doesn't
   *  conflict with ones used by the Form API or other modules is to use the
   *  module name as the key name or a prefix for the key name. For example, the
   *  entity form classes use $this->entity in entity forms, or
   *  $form_state->getFormObject()->getEntity() outside the controller, to store
   *  information about the entity being edited, and this information stays
   *  available across successive clicks of the "Preview" button (if available)
   *  as well as when the "Save" button is finally clicked.
   *
   * @var array
   */
  protected $storage = array();

  /**
   * A list containing copies of all submit and button elements in the form.
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $buttons = array();

  /**
   * Holds temporary data accessible during the current page request only.
   *
   * All $form_state properties that are not reserved keys (see
   * other properties marked as uncacheable) persist throughout a multistep form
   * sequence. Form API provides this key for modules to communicate information
   * across form-related functions during a single page request. It may be used
   * to temporarily save data that does not need to or should not be cached
   * during the whole form workflow; e.g., data that needs to be accessed during
   * the current form build process only. There is no use-case for this
   * functionality in Drupal core.
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $temporary;

  /**
   * Tracks if the form has finished validation.
   *
   * This property is uncacheable.
   *
   * @var bool
   */
  protected $validation_complete = FALSE;

  /**
   * Contains errors for this form.
   *
   * This property is uncacheable.
   *
   * @var array
   */
  protected $errors = array();

  /**
   * Stores which errors should be limited during validation.
   *
   * An array of "sections" within which user input must be valid. If the
   * element is within one of these sections, the error must be recorded.
   * Otherwise, it can be suppressed. self::$limit_validation_errors can be an
   * empty array, in which case all errors are suppressed. For example, a
   * "Previous" button might want its submit action to be triggered even if none
   * of the submitted values are valid.
   *
   * This property is uncacheable.
   *
   * @var array|null
   */
  protected $limit_validation_errors;

  /**
   * Stores the gathered validation handlers.
   *
   * This property is uncacheable.
   *

  /**
   * Stores the gathered submission handlers.
   *
   * This property is uncacheable.
   *
  public function setFormState(array $form_state_additions) {
    foreach ($form_state_additions as $key => $value) {
      if (property_exists($this, $key)) {
        $this->{$key} = $value;
      }
      else {
        $this->set($key, $value);
      }
    }
    return $this;
  public function setAlwaysProcess($always_process = TRUE) {
    $this->always_process = (bool) $always_process;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getAlwaysProcess() {
    return $this->always_process;
  }

  /**
   * {@inheritdoc}
   */
  public function setButtons(array $buttons) {
    $this->buttons = $buttons;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getButtons() {
    return $this->buttons;
  }

  /**
   * {@inheritdoc}
   */
  public function setCached($cache = TRUE) {
    $this->cache = (bool) $cache;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isCached() {
    return empty($this->no_cache) && $this->cache;
  }

  /**
   * {@inheritdoc}
   */
  public function disableCache() {
    $this->no_cache = TRUE;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setExecuted() {
    $this->executed = TRUE;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isExecuted() {
    return $this->executed;
  }

  /**
   * {@inheritdoc}
   */
  public function setGroups(array $groups) {
    $this->groups = $groups;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function &getGroups() {
    return $this->groups;
  }

  /**
   * {@inheritdoc}
   */
  public function setHasFileElement($has_file_element = TRUE) {
    $this->has_file_element = (bool) $has_file_element;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function hasFileElement() {
    return $this->has_file_element;
  }

  /**
   * {@inheritdoc}
   */
  public function setLimitValidationErrors($limit_validation_errors) {
    $this->limit_validation_errors = $limit_validation_errors;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getLimitValidationErrors() {
    return $this->limit_validation_errors;
  }

  /**
   * {@inheritdoc}
   */
  public function setMethod($method) {
    $this->method = strtoupper($method);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isMethodType($method_type) {
    return $this->method === strtoupper($method_type);
  }

  /**
   * {@inheritdoc}
   */
  public function setValidationEnforced($must_validate = TRUE) {
    $this->must_validate = (bool) $must_validate;
  /**
   * {@inheritdoc}
   */
  public function isValidationEnforced() {
    return $this->must_validate;
  }

  /**
   * {@inheritdoc}
   */
  public function disableRedirect($no_redirect = TRUE) {
    $this->no_redirect = (bool) $no_redirect;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isRedirectDisabled() {
   return $this->no_redirect;
  }

  /**
   * {@inheritdoc}
   */
  public function setProcessInput($process_input = TRUE) {
    $this->process_input = (bool) $process_input;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isProcessingInput() {
    return $this->process_input;
  }

  /**
   * {@inheritdoc}
   */
  public function setProgrammed($programmed = TRUE) {
    $this->programmed = (bool) $programmed;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isProgrammed() {
    return $this->programmed;
  }

  /**
   * {@inheritdoc}
   */
  public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE) {
    $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isBypassingProgrammedAccessChecks() {
    return $this->programmed_bypass_access_check;
  }

  /**
   * {@inheritdoc}
   */
  public function setRebuildInfo(array $rebuild_info) {
    $this->rebuild_info = $rebuild_info;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getRebuildInfo() {
    return $this->rebuild_info;
  }

  /**
   * {@inheritdoc}
   */
  public function addRebuildInfo($property, $value) {
    $rebuild_info = $this->getRebuildInfo();
    $rebuild_info[$property] = $value;
    $this->setRebuildInfo($rebuild_info);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setStorage(array $storage) {
    $this->storage = $storage;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function &getStorage() {
    return $this->storage;
  }

  /**
   * {@inheritdoc}
   */
  public function setSubmitHandlers(array $submit_handlers) {
    $this->submit_handlers = $submit_handlers;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getSubmitHandlers() {
    return $this->submit_handlers;
  }

  /**
   * {@inheritdoc}
   */
  public function setSubmitted() {
    $this->submitted = TRUE;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isSubmitted() {
    return $this->submitted;
  }

  /**
   * {@inheritdoc}
   */
  public function setTemporary(array $temporary) {
    $this->temporary = $temporary;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getTemporary() {
    return $this->temporary;
  }

  /**
   * {@inheritdoc}
   */
  public function setTriggeringElement($triggering_element) {
    $this->triggering_element = $triggering_element;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function &getTriggeringElement() {
    return $this->triggering_element;
  }

  /**
   * {@inheritdoc}
   */
  public function setValidateHandlers(array $validate_handlers) {
    $this->validate_handlers = $validate_handlers;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getValidateHandlers() {
    return $this->validate_handlers;
  }

  /**
   * {@inheritdoc}
   */
  public function setValidationComplete($validation_complete = TRUE) {
    $this->validation_complete = (bool) $validation_complete;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isValidationComplete() {
    return $this->validation_complete;
  }

  /**
   * {@inheritdoc}
   */
  public function loadInclude($module, $type, $name = NULL) {
    if (!isset($name)) {
      $name = $module;
    }
    $build_info = $this->getBuildInfo();
    if (!isset($build_info['files']["$module:$name.$type"])) {
      // Only add successfully included files to the form state.
      if ($result = $this->moduleLoadInclude($module, $type, $name)) {
        $build_info['files']["$module:$name.$type"] = array(
          'type' => $type,
          'module' => $module,
          'name' => $name,
        );
  public function getCacheableArray() {
    return [
      'build_info' => $this->getBuildInfo(),
      'response' => $this->getResponse(),
      'programmed' => $this->isProgrammed(),
      'programmed_bypass_access_check' => $this->isBypassingProgrammedAccessChecks(),
      'process_input' => $this->isProcessingInput(),
      'has_file_element' => $this->hasFileElement(),
      'storage' => $this->getStorage(),
      // Use the properties directly, since self::isCached() combines them and
      // cannot be relied upon.
      'cache' => $this->cache,
      'no_cache' => $this->no_cache,
  }

  /**
   * {@inheritdoc}
   */
  public function setCompleteForm(array &$complete_form) {
    $this->complete_form = &$complete_form;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function &getCompleteForm() {
    return $this->complete_form;
  }

  /**
   * {@inheritdoc}
   */
  public function &get($property) {
    $value = &NestedArray::getValue($this->storage, (array) $property);
    return $value;
  }

  /**
   * {@inheritdoc}
   */
  public function set($property, $value) {
    NestedArray::setValue($this->storage, (array) $property, $value, TRUE);
  public function has($property) {
    $exists = NULL;
    NestedArray::getValue($this->storage, (array) $property, $exists);
    return $exists;
  public function setBuildInfo(array $build_info) {
    $this->build_info = $build_info;
  public function getBuildInfo() {
    return $this->build_info;
  }

  /**
   * {@inheritdoc}
   */
  public function addBuildInfo($property, $value) {
    $build_info = $this->getBuildInfo();
  /**
   * {@inheritdoc}
   */
  public function &getUserInput() {
    return $this->input;
  }

  /**
   * {@inheritdoc}
   */
  public function setUserInput(array $user_input) {
    $this->input = $user_input;
    return $this;
  }

  public function &getValues() {
    return $this->values;
  public function &getValue($key, $default = NULL) {
    $exists = NULL;
    $value = &NestedArray::getValue($this->getValues(), (array) $key, $exists);
    if (!$exists) {
      $value = $default;
    }
    return $value;
  }

  /**
   * {@inheritdoc}
   */
  public function setValues(array $values) {
    $this->values = $values;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setValue($key, $value) {
    NestedArray::setValue($this->getValues(), (array) $key, $value, TRUE);
  public function unsetValue($key) {
    NestedArray::unsetValue($this->getValues(), (array) $key);
  /**
   * {@inheritdoc}
   */
  public function hasValue($key) {
    $exists = NULL;
    $value = NestedArray::getValue($this->getValues(), (array) $key, $exists);
    return $exists && isset($value);
  }

  /**
   * {@inheritdoc}
   */
  public function isValueEmpty($key) {
    $exists = NULL;
    $value = NestedArray::getValue($this->getValues(), (array) $key, $exists);
    return !$exists || empty($value);
  }

  /**
   * {@inheritdoc}
   */
  public function setValueForElement(array $element, $value) {
    return $this->setValue($element['#parents'], $value);
  }

  /**
   * {@inheritdoc}
   */
  public function setResponse(Response $response) {
  /**
   * {@inheritdoc}
   */
  public function getResponse() {
    return $this->response;
  }

  public function setRedirect($route_name, array $route_parameters = array(), array $options = array()) {
    $url = new Url($route_name, $route_parameters, $options);
    return $this->setRedirectUrl($url);
  }

  /**
   * {@inheritdoc}
   */
  public function setRedirectUrl(Url $url) {
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getRedirect() {
    // Skip redirection for form submissions invoked via
    // \Drupal\Core\Form\FormBuilderInterface::submitForm().
      return FALSE;
    }
    // Skip redirection if rebuild is activated.
      return FALSE;
    }
    // Skip redirection if it was explicitly disallowed.