3 namespace Drupal\Core\Form;
5 use Drupal\Component\Utility\NestedArray;
7 use Symfony\Component\HttpFoundation\Response;
10 * Stores information about the state of a form.
12 class FormState implements FormStateInterface {
14 use FormStateValuesTrait;
17 * Tracks if any errors have been set on any form.
21 protected static $anyErrors = FALSE;
24 * The complete form structure.
26 * #process, #after_build, #element_validate, and other handlers being invoked
27 * on a form element may use this reference to access other information in the
28 * form the element is contained in.
30 * @see self::getCompleteForm()
32 * This property is uncacheable.
36 protected $complete_form;
39 * An associative array of information stored by Form API that is necessary to
40 * build and rebuild the form from cache when the original context may no
41 * longer be available:
42 * - callback: The actual callback to be used to retrieve the form array.
43 * Can be any callable. If none is provided $form_id is used as the name
44 * of a function to call instead.
45 * - args: A list of arguments to pass to the form constructor.
46 * - files: An optional array defining include files that need to be loaded
47 * for building the form. Each array entry may be the path to a file or
48 * another array containing values for the parameters 'type', 'module' and
49 * 'name' as needed by module_load_include(). The files listed here are
50 * automatically loaded by \Drupal::formBuilder()->getCache(). By default
51 * the current menu router item's 'file' definition is added, if any. Use
52 * self::loadInclude() to add include files from a form constructor.
53 * - form_id: Identification of the primary form being constructed and
55 * - base_form_id: Identification for a base form, as declared in the form
56 * class's \Drupal\Core\Form\BaseFormIdInterface::getBaseFormId() method.
57 * - immutable: If this flag is set to TRUE, a new form build id is
58 * generated when the form is loaded from the cache. If it is subsequently
59 * saved to the cache again, it will have another cache id and therefore
60 * the original form and form-state will remain unaltered. This is
61 * important when page caching is enabled in order to prevent form state
62 * from leaking between anonymous users.
66 protected $build_info = [
72 * Similar to self::$build_info, but pertaining to
73 * \Drupal\Core\Form\FormBuilderInterface::rebuildForm().
75 * This property is uncacheable.
79 protected $rebuild_info = [];
82 * Normally, after the entire form processing is completed and submit handlers
83 * have run, a form is considered to be done and
84 * \Drupal\Core\Form\FormSubmitterInterface::redirectForm() will redirect the
85 * user to a new page using a GET request (so a browser refresh does not
86 * re-submit the form). However, if 'rebuild' has been set to TRUE, then a new
87 * copy of the form is immediately built and sent to the browser, instead of a
88 * redirect. This is used for multi-step forms, such as wizards and
89 * confirmation forms. Normally, self::$rebuild is set by a submit handler,
90 * since its is usually logic within a submit handler that determines whether
91 * a form is done or requires another step. However, a validation handler may
92 * already set self::$rebuild to cause the form processing to bypass submit
93 * handlers and rebuild the form instead, even if there are no validation
96 * This property is uncacheable.
98 * @see self::setRebuild()
102 protected $rebuild = FALSE;
105 * If set to TRUE the form will skip calling form element value callbacks,
106 * except for a select list of callbacks provided by Drupal core that are
109 * This property is uncacheable.
111 * @see self::setInvalidToken()
115 protected $invalidToken = FALSE;
118 * Used when a form needs to return some kind of a
119 * \Symfony\Component\HttpFoundation\Response object, e.g., a
120 * \Symfony\Component\HttpFoundation\BinaryFileResponse when triggering a
121 * file download. If you use self::setRedirect() or self::setRedirectUrl(),
122 * it will be used to build a
123 * \Symfony\Component\HttpFoundation\RedirectResponse and will populate this
126 * @var \Symfony\Component\HttpFoundation\Response|null
131 * Used to redirect the form on submission.
133 * @see self::getRedirect()
135 * This property is uncacheable.
137 * @var \Drupal\Core\Url|\Symfony\Component\HttpFoundation\RedirectResponse|null
142 * If set to TRUE the form will NOT perform a redirect, even if
143 * self::$redirect is set.
145 * This property is uncacheable.
149 protected $no_redirect;
152 * The HTTP form method to use for finding the input for this form.
154 * May be 'POST' or 'GET'. Defaults to 'POST'. Note that 'GET' method forms do
155 * not use form ids so are always considered to be submitted, which can have
156 * unexpected effects. The 'GET' method should only be used on forms that do
157 * not change data, as that is exclusively the domain of 'POST.'
159 * This property is uncacheable.
163 protected $method = 'POST';
166 * The HTTP method used by the request building or processing this form.
168 * May be any valid HTTP method. Defaults to 'GET', because even though
169 * $method is 'POST' for most forms, the form's initial build is usually
170 * performed as part of a GET request.
172 * This property is uncacheable.
176 protected $requestMethod = 'GET';
179 * If set to TRUE the original, unprocessed form structure will be cached,
180 * which allows the entire form to be rebuilt from cache. A typical form
181 * workflow involves two page requests; first, a form is built and rendered
182 * for the user to fill in. Then, the user fills the form in and submits it,
183 * triggering a second page request in which the form must be built and
184 * processed. By default, $form and $form_state are built from scratch during
185 * each of these page requests. Often, it is necessary or desired to persist
186 * the $form and $form_state variables from the initial page request to the
187 * one that processes the submission. 'cache' can be set to TRUE to do this.
188 * A prominent example is an Ajax-enabled form, in which
189 * \Drupal\Core\Render\Element\RenderElement::processAjaxForm()
190 * enables form caching for all forms that include an element with the #ajax
191 * property. (The Ajax handler has no way to build the form itself, so must
192 * rely on the cached version.) Note that the persistence of $form and
193 * $form_state happens automatically for (multi-step) forms having the
194 * self::$rebuild flag set, regardless of the value for self::$cache.
198 protected $cache = FALSE;
201 * If set to TRUE the form will NOT be cached, even if 'cache' is set.
208 * An associative array of values submitted to the form.
210 * The validation functions and submit functions use this array for nearly all
211 * their decision making. (Note that #tree determines whether the values are a
212 * flat array or an array whose structure parallels the $form array. See
213 * \Drupal\Core\Render\Element\FormElement for more information.)
215 * This property is uncacheable.
219 protected $values = [];
222 * An associative array of form value keys to be removed by cleanValues().
224 * Any values that are temporary but must still be displayed as values in
225 * the rendered form should be added to this array using addCleanValueKey().
226 * Initialized with internal Form API values.
228 * This property is uncacheable.
232 protected $cleanValueKeys = [
240 * The array of values as they were submitted by the user.
242 * These are raw and unvalidated, so should not be used without a thorough
243 * understanding of security implications. In almost all cases, code should
244 * use the data in the 'values' array exclusively. The most common use of this
245 * key is for multi-step forms that need to clear some of the user input when
246 * setting 'rebuild'. The values correspond to \Drupal::request()->request or
247 * \Drupal::request()->query, depending on the 'method' chosen.
249 * This property is uncacheable.
252 * The submitted user input array, or NULL if no input was submitted yet.
257 * If TRUE and the method is GET, a form_id is not necessary.
259 * This property is uncacheable.
263 protected $always_process;
266 * Ordinarily, a form is only validated once, but there are times when a form
267 * is resubmitted internally and should be validated again. Setting this to
268 * TRUE will force that to happen. This is most likely to occur during Ajax
271 * This property is uncacheable.
275 protected $must_validate;
278 * If TRUE, the form was submitted programmatically, usually invoked via
279 * \Drupal\Core\Form\FormBuilderInterface::submitForm(). Defaults to FALSE.
283 protected $programmed = FALSE;
286 * If TRUE, programmatic form submissions are processed without taking #access
287 * into account. Set this to FALSE when submitting a form programmatically
288 * with values that may have been input by the user executing the current
289 * request; this will cause #access to be respected as it would on a normal
290 * form submission. Defaults to TRUE.
294 protected $programmed_bypass_access_check = TRUE;
297 * TRUE signifies correct form submission. This is always TRUE for programmed
298 * forms coming from \Drupal\Core\Form\FormBuilderInterface::submitForm() (see
299 * 'programmed' key), or if the form_id coming from the
300 * \Drupal::request()->request data is set and matches the current form_id.
304 protected $process_input;
307 * If TRUE, the form has been submitted. Defaults to FALSE.
309 * This property is uncacheable.
313 protected $submitted = FALSE;
316 * If TRUE, the form was submitted and has been processed and executed.
318 * This property is uncacheable.
322 protected $executed = FALSE;
325 * The form element that triggered submission, which may or may not be a
326 * button (in the case of Ajax forms). This key is often used to distinguish
327 * between various buttons in a submit handler, and is also used in Ajax
330 * This property is uncacheable.
334 protected $triggering_element;
337 * If TRUE, there is a file element and Form API will set the appropriate
338 * 'enctype' HTML attribute on the form.
342 protected $has_file_element;
345 * Contains references to details elements to render them within vertical tabs.
347 * This property is uncacheable.
351 protected $groups = [];
354 * This is not a special key, and no specific support is provided for it in
355 * the Form API. By tradition it was the location where application-specific
356 * data was stored for communication between the submit, validation, and form
357 * builder functions, especially in a multi-step-style form. Form
358 * implementations may use any key(s) within $form_state (other than the keys
359 * listed here and other reserved ones used by Form API internals) for this
360 * kind of storage. The recommended way to ensure that the chosen key doesn't
361 * conflict with ones used by the Form API or other modules is to use the
362 * module name as the key name or a prefix for the key name. For example, the
363 * entity form classes use $this->entity in entity forms, or
364 * $form_state->getFormObject()->getEntity() outside the controller, to store
365 * information about the entity being edited, and this information stays
366 * available across successive clicks of the "Preview" button (if available)
367 * as well as when the "Save" button is finally clicked.
371 protected $storage = [];
374 * A list containing copies of all submit and button elements in the form.
376 * This property is uncacheable.
380 protected $buttons = [];
383 * Holds temporary data accessible during the current page request only.
385 * All $form_state properties that are not reserved keys (see
386 * other properties marked as uncacheable) persist throughout a multistep form
387 * sequence. Form API provides this key for modules to communicate information
388 * across form-related functions during a single page request. It may be used
389 * to temporarily save data that does not need to or should not be cached
390 * during the whole form workflow; e.g., data that needs to be accessed during
391 * the current form build process only. There is no use-case for this
392 * functionality in Drupal core.
394 * This property is uncacheable.
398 protected $temporary = [];
401 * Tracks if the form has finished validation.
403 * This property is uncacheable.
407 protected $validation_complete = FALSE;
410 * Contains errors for this form.
412 * This property is uncacheable.
416 protected $errors = [];
419 * Stores which errors should be limited during validation.
421 * An array of "sections" within which user input must be valid. If the
422 * element is within one of these sections, the error must be recorded.
423 * Otherwise, it can be suppressed. self::$limit_validation_errors can be an
424 * empty array, in which case all errors are suppressed. For example, a
425 * "Previous" button might want its submit action to be triggered even if none
426 * of the submitted values are valid.
428 * This property is uncacheable.
432 protected $limit_validation_errors;
435 * Stores the gathered validation handlers.
437 * This property is uncacheable.
441 protected $validate_handlers = [];
444 * Stores the gathered submission handlers.
446 * This property is uncacheable.
450 protected $submit_handlers = [];
455 public function setFormState(array $form_state_additions) {
456 foreach ($form_state_additions as $key => $value) {
457 if (property_exists($this, $key)) {
458 $this->{$key} = $value;
461 $this->set($key, $value);
470 public function setAlwaysProcess($always_process = TRUE) {
471 $this->always_process = (bool) $always_process;
478 public function getAlwaysProcess() {
479 return $this->always_process;
485 public function setButtons(array $buttons) {
486 $this->buttons = $buttons;
493 public function getButtons() {
494 return $this->buttons;
500 public function setCached($cache = TRUE) {
501 // Persisting $form_state is a side-effect disallowed during a "safe" HTTP
503 if ($cache && $this->isRequestMethodSafe()) {
504 throw new \LogicException(sprintf('Form state caching on %s requests is not allowed.', $this->requestMethod));
507 $this->cache = (bool) $cache;
514 public function isCached() {
515 return empty($this->no_cache) && $this->cache;
521 public function disableCache() {
522 $this->no_cache = TRUE;
529 public function setExecuted() {
530 $this->executed = TRUE;
537 public function isExecuted() {
538 return $this->executed;
544 public function setGroups(array $groups) {
545 $this->groups = $groups;
552 public function &getGroups() {
553 return $this->groups;
559 public function setHasFileElement($has_file_element = TRUE) {
560 $this->has_file_element = (bool) $has_file_element;
567 public function hasFileElement() {
568 return $this->has_file_element;
574 public function setLimitValidationErrors($limit_validation_errors) {
575 $this->limit_validation_errors = $limit_validation_errors;
582 public function getLimitValidationErrors() {
583 return $this->limit_validation_errors;
589 public function setMethod($method) {
590 $this->method = strtoupper($method);
597 public function isMethodType($method_type) {
598 return $this->method === strtoupper($method_type);
604 public function setRequestMethod($method) {
605 $this->requestMethod = strtoupper($method);
610 * Checks whether the request method is a "safe" HTTP method.
612 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 defines
613 * GET and HEAD as "safe" methods, meaning they SHOULD NOT have side-effects,
614 * such as persisting $form_state changes.
618 * @see \Symfony\Component\HttpFoundation\Request::isMethodSafe()
620 protected function isRequestMethodSafe() {
621 return in_array($this->requestMethod, ['GET', 'HEAD']);
627 public function setValidationEnforced($must_validate = TRUE) {
628 $this->must_validate = (bool) $must_validate;
635 public function isValidationEnforced() {
636 return $this->must_validate;
642 public function disableRedirect($no_redirect = TRUE) {
643 $this->no_redirect = (bool) $no_redirect;
650 public function isRedirectDisabled() {
651 return $this->no_redirect;
657 public function setProcessInput($process_input = TRUE) {
658 $this->process_input = (bool) $process_input;
665 public function isProcessingInput() {
666 return $this->process_input;
672 public function setProgrammed($programmed = TRUE) {
673 $this->programmed = (bool) $programmed;
680 public function isProgrammed() {
681 return $this->programmed;
687 public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE) {
688 $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check;
695 public function isBypassingProgrammedAccessChecks() {
696 return $this->programmed_bypass_access_check;
702 public function setRebuildInfo(array $rebuild_info) {
703 $this->rebuild_info = $rebuild_info;
710 public function getRebuildInfo() {
711 return $this->rebuild_info;
717 public function addRebuildInfo($property, $value) {
718 $rebuild_info = $this->getRebuildInfo();
719 $rebuild_info[$property] = $value;
720 $this->setRebuildInfo($rebuild_info);
727 public function setStorage(array $storage) {
728 $this->storage = $storage;
735 public function &getStorage() {
736 return $this->storage;
742 public function setSubmitHandlers(array $submit_handlers) {
743 $this->submit_handlers = $submit_handlers;
750 public function getSubmitHandlers() {
751 return $this->submit_handlers;
757 public function setSubmitted() {
758 $this->submitted = TRUE;
765 public function isSubmitted() {
766 return $this->submitted;
772 public function setTemporary(array $temporary) {
773 $this->temporary = $temporary;
780 public function getTemporary() {
781 return $this->temporary;
787 public function &getTemporaryValue($key) {
788 $value = &NestedArray::getValue($this->temporary, (array) $key);
795 public function setTemporaryValue($key, $value) {
796 NestedArray::setValue($this->temporary, (array) $key, $value, TRUE);
803 public function hasTemporaryValue($key) {
805 NestedArray::getValue($this->temporary, (array) $key, $exists);
812 public function setTriggeringElement($triggering_element) {
813 $this->triggering_element = $triggering_element;
820 public function &getTriggeringElement() {
821 return $this->triggering_element;
827 public function setValidateHandlers(array $validate_handlers) {
828 $this->validate_handlers = $validate_handlers;
835 public function getValidateHandlers() {
836 return $this->validate_handlers;
842 public function setValidationComplete($validation_complete = TRUE) {
843 $this->validation_complete = (bool) $validation_complete;
850 public function isValidationComplete() {
851 return $this->validation_complete;
857 public function loadInclude($module, $type, $name = NULL) {
861 $build_info = $this->getBuildInfo();
862 if (!isset($build_info['files']["$module:$name.$type"])) {
863 // Only add successfully included files to the form state.
864 if ($result = $this->moduleLoadInclude($module, $type, $name)) {
865 $build_info['files']["$module:$name.$type"] = [
870 $this->setBuildInfo($build_info);
880 public function getCacheableArray() {
882 'build_info' => $this->getBuildInfo(),
883 'response' => $this->getResponse(),
884 'programmed' => $this->isProgrammed(),
885 'programmed_bypass_access_check' => $this->isBypassingProgrammedAccessChecks(),
886 'process_input' => $this->isProcessingInput(),
887 'has_file_element' => $this->hasFileElement(),
888 'storage' => $this->getStorage(),
889 // Use the properties directly, since self::isCached() combines them and
890 // cannot be relied upon.
891 'cache' => $this->cache,
892 'no_cache' => $this->no_cache,
899 public function setCompleteForm(array &$complete_form) {
900 $this->complete_form = &$complete_form;
907 public function &getCompleteForm() {
908 return $this->complete_form;
914 public function &get($property) {
915 $value = &NestedArray::getValue($this->storage, (array) $property);
922 public function set($property, $value) {
923 NestedArray::setValue($this->storage, (array) $property, $value, TRUE);
930 public function has($property) {
932 NestedArray::getValue($this->storage, (array) $property, $exists);
939 public function setBuildInfo(array $build_info) {
940 $this->build_info = $build_info;
947 public function getBuildInfo() {
948 return $this->build_info;
954 public function addBuildInfo($property, $value) {
955 $build_info = $this->getBuildInfo();
956 $build_info[$property] = $value;
957 $this->setBuildInfo($build_info);
964 public function &getUserInput() {
971 public function setUserInput(array $user_input) {
972 $this->input = $user_input;
979 public function &getValues() {
980 return $this->values;
986 public function setResponse(Response $response) {
987 $this->response = $response;
994 public function getResponse() {
995 return $this->response;
1001 public function setRedirect($route_name, array $route_parameters = [], array $options = []) {
1002 $url = new Url($route_name, $route_parameters, $options);
1003 return $this->setRedirectUrl($url);
1009 public function setRedirectUrl(Url $url) {
1010 $this->redirect = $url;
1017 public function getRedirect() {
1018 // Skip redirection for form submissions invoked via
1019 // \Drupal\Core\Form\FormBuilderInterface::submitForm().
1020 if ($this->isProgrammed()) {
1023 // Skip redirection if rebuild is activated.
1024 if ($this->isRebuilding()) {
1027 // Skip redirection if it was explicitly disallowed.
1028 if ($this->isRedirectDisabled()) {
1032 return $this->redirect;
1036 * Sets the global status of errors.
1038 * @param bool $errors
1039 * TRUE if any form has any errors, FALSE otherwise.
1041 protected static function setAnyErrors($errors = TRUE) {
1042 static::$anyErrors = $errors;
1048 public static function hasAnyErrors() {
1049 return static::$anyErrors;
1055 public function setErrorByName($name, $message = '') {
1056 if ($this->isValidationComplete()) {
1057 throw new \LogicException('Form errors cannot be set after form validation has finished.');
1060 $errors = $this->getErrors();
1061 if (!isset($errors[$name])) {
1063 $limit_validation_errors = $this->getLimitValidationErrors();
1064 if ($limit_validation_errors !== NULL) {
1066 foreach ($limit_validation_errors as $section) {
1067 // Exploding by '][' reconstructs the element's #parents. If the
1068 // reconstructed #parents begin with the same keys as the specified
1069 // section, then the element's values are within the part of
1070 // $form_state->getValues() that the clicked button requires to be
1071 // valid, so errors for this element must be recorded. As the exploded
1072 // array will all be strings, we need to cast every value of the
1073 // section array to string.
1074 if (array_slice(explode('][', $name), 0, count($section)) === array_map('strval', $section)) {
1081 $errors[$name] = $message;
1082 $this->errors = $errors;
1083 static::setAnyErrors();
1093 public function setError(array &$element, $message = '') {
1094 $this->setErrorByName(implode('][', $element['#parents']), $message);
1101 public function clearErrors() {
1103 static::setAnyErrors(FALSE);
1109 public function getError(array $element) {
1110 if ($errors = $this->getErrors()) {
1112 foreach ($element['#parents'] as $parent) {
1113 $parents[] = $parent;
1114 $key = implode('][', $parents);
1115 if (isset($errors[$key])) {
1116 return $errors[$key];
1125 public function getErrors() {
1126 return $this->errors;
1132 public function setRebuild($rebuild = TRUE) {
1133 $this->rebuild = $rebuild;
1140 public function isRebuilding() {
1141 return $this->rebuild;
1147 public function prepareCallback($callback) {
1148 if (is_string($callback) && substr($callback, 0, 2) == '::') {
1149 $callback = [$this->getFormObject(), substr($callback, 2)];
1157 public function setFormObject(FormInterface $form_object) {
1158 $this->addBuildInfo('callback_object', $form_object);
1165 public function getFormObject() {
1166 return $this->getBuildInfo()['callback_object'];
1172 public function getCleanValueKeys() {
1173 return $this->cleanValueKeys;
1179 public function setCleanValueKeys(array $cleanValueKeys) {
1180 $this->cleanValueKeys = $cleanValueKeys;
1187 public function addCleanValueKey($cleanValueKey) {
1188 $keys = $this->getCleanValueKeys();
1189 $this->setCleanValueKeys(array_merge((array)$keys, [$cleanValueKey]));
1196 public function cleanValues() {
1197 foreach ($this->getCleanValueKeys() as $value) {
1198 $this->unsetValue($value);
1201 // Remove button values.
1202 // \Drupal::formBuilder()->doBuildForm() collects all button elements in a
1203 // form. We remove the button value separately for each button element.
1204 foreach ($this->getButtons() as $button) {
1205 // Remove this button's value from the submitted form values by finding
1206 // the value corresponding to this button.
1207 // We iterate over the #parents of this button and move a reference to
1208 // each parent in self::getValues(). For example, if #parents is:
1209 // array('foo', 'bar', 'baz')
1210 // then the corresponding self::getValues() part will look like this:
1214 // 'baz' => 'button_value',
1218 // We start by (re)moving 'baz' to $last_parent, so we are able unset it
1219 // at the end of the iteration. Initially, $values will contain a
1220 // reference to self::getValues(), but in the iteration we move the
1221 // reference to self::getValue('foo'), and finally to
1222 // self::getValue(array('foo', 'bar')), which is the level where we
1223 // can unset 'baz' (that is stored in $last_parent).
1224 $parents = $button['#parents'];
1225 $last_parent = array_pop($parents);
1227 $values = &NestedArray::getValue($this->getValues(), $parents, $key_exists);
1228 if ($key_exists && is_array($values)) {
1229 unset($values[$last_parent]);
1238 public function setInvalidToken($invalid_token) {
1239 $this->invalidToken = (bool) $invalid_token;
1246 public function hasInvalidToken() {
1247 return $this->invalidToken;
1251 * Wraps ModuleHandler::loadInclude().
1253 protected function moduleLoadInclude($module, $type, $name = NULL) {
1254 return \Drupal::moduleHandler()->loadInclude($module, $type, $name);