1<?php 2 3namespace Drupal\Core\Form; 4 5use Drupal\Component\Utility\NestedArray; 6use Drupal\Core\Url; 7use Symfony\Component\HttpFoundation\Response; 8 9/** 10 * Stores information about the state of a form. 11 */ 12class FormState implements FormStateInterface { 13 14 use FormStateValuesTrait; 15 16 /** 17 * Tracks if any errors have been set on any form. 18 * 19 * @var bool 20 */ 21 protected static $anyErrors = FALSE; 22 23 /** 24 * The complete form structure. 25 * 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. 29 * 30 * @see self::getCompleteForm() 31 * 32 * This property is uncacheable. 33 * 34 * @var array 35 */ 36 protected $complete_form; 37 38 /** 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 54 * processed. 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. 63 * 64 * @var array 65 */ 66 protected $build_info = [ 67 'args' => [], 68 'files' => [], 69 ]; 70 71 /** 72 * Similar to self::$build_info, but pertaining to 73 * \Drupal\Core\Form\FormBuilderInterface::rebuildForm(). 74 * 75 * This property is uncacheable. 76 * 77 * @var array 78 */ 79 protected $rebuild_info = []; 80 81 /** 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 it is usually logic within a submit handler that determines whether a 91 * 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 94 * errors. 95 * 96 * This property is uncacheable. 97 * 98 * @see self::setRebuild() 99 * 100 * @var bool 101 */ 102 protected $rebuild = FALSE; 103 104 /** 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 107 * known to be safe. 108 * 109 * This property is uncacheable. 110 * 111 * @see self::setInvalidToken() 112 * 113 * @var bool 114 */ 115 protected $invalidToken = FALSE; 116 117 /** 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 124 * key. 125 * 126 * @var \Symfony\Component\HttpFoundation\Response|null 127 */ 128 protected $response; 129 130 /** 131 * Used to redirect the form on submission. 132 * 133 * @see self::getRedirect() 134 * 135 * This property is uncacheable. 136 * 137 * @var \Drupal\Core\Url|\Symfony\Component\HttpFoundation\RedirectResponse|null 138 */ 139 protected $redirect; 140 141 /** 142 * If set to TRUE the form will NOT perform a redirect, even if 143 * self::$redirect is set. 144 * 145 * This property is uncacheable. 146 * 147 * @var bool 148 */ 149 protected $no_redirect; 150 151 /** 152 * The HTTP form method to use for finding the input for this form. 153 * 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.' 158 * 159 * This property is uncacheable. 160 * 161 * @var string 162 */ 163 protected $method = 'POST'; 164 165 /** 166 * The HTTP method used by the request building or processing this form. 167 * 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. 171 * 172 * This property is uncacheable. 173 * 174 * @var string 175 */ 176 protected $requestMethod = 'GET'; 177 178 /** 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. 195 * 196 * @var bool 197 */ 198 protected $cache = FALSE; 199 200 /** 201 * If set to TRUE the form will NOT be cached, even if 'cache' is set. 202 * 203 * @var bool 204 */ 205 protected $no_cache; 206 207 /** 208 * An associative array of values submitted to the form. 209 * 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.) 214 * 215 * This property is uncacheable. 216 * 217 * @var array 218 */ 219 protected $values = []; 220 221 /** 222 * An associative array of form value keys to be removed by cleanValues(). 223 * 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. 227 * 228 * This property is uncacheable. 229 * 230 * @var array 231 */ 232 protected $cleanValueKeys = [ 233 'form_id', 234 'form_token', 235 'form_build_id', 236 'op', 237 ]; 238 239 /** 240 * The array of values as they were submitted by the user. 241 * 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. 248 * 249 * This property is uncacheable. 250 * 251 * @var array|null 252 * The submitted user input array, or NULL if no input was submitted yet. 253 */ 254 protected $input; 255 256 /** 257 * If TRUE and the method is GET, a form_id is not necessary. 258 * 259 * This property is uncacheable. 260 * 261 * @var bool 262 */ 263 protected $always_process; 264 265 /** 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 269 * operations. 270 * 271 * This property is uncacheable. 272 * 273 * @var bool 274 */ 275 protected $must_validate; 276 277 /** 278 * If TRUE, the form was submitted programmatically, usually invoked via 279 * \Drupal\Core\Form\FormBuilderInterface::submitForm(). Defaults to FALSE. 280 * 281 * @var bool 282 */ 283 protected $programmed = FALSE; 284 285 /** 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. 291 * 292 * @var bool 293 */ 294 protected $programmed_bypass_access_check = TRUE; 295 296 /** 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. 301 * 302 * @var bool 303 */ 304 protected $process_input; 305 306 /** 307 * If TRUE, the form has been submitted. Defaults to FALSE. 308 * 309 * This property is uncacheable. 310 * 311 * @var bool 312 */ 313 protected $submitted = FALSE; 314 315 /** 316 * If TRUE, the form was submitted and has been processed and executed. 317 * 318 * This property is uncacheable. 319 * 320 * @var bool 321 */ 322 protected $executed = FALSE; 323 324 /** 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 328 * handlers. 329 * 330 * This property is uncacheable. 331 * 332 * @var array|null 333 */ 334 protected $triggering_element; 335 336 /** 337 * If TRUE, there is a file element and Form API will set the appropriate 338 * 'enctype' HTML attribute on the form. 339 * 340 * @var bool 341 */ 342 protected $has_file_element; 343 344 /** 345 * Contains references to details elements to render them within vertical tabs. 346 * 347 * This property is uncacheable. 348 * 349 * @var array 350 */ 351 protected $groups = []; 352 353 /** 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. 368 * 369 * @var array 370 */ 371 protected $storage = []; 372 373 /** 374 * A list containing copies of all submit and button elements in the form. 375 * 376 * This property is uncacheable. 377 * 378 * @var array 379 */ 380 protected $buttons = []; 381 382 /** 383 * Holds temporary data accessible during the current page request only. 384 * 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. 393 * 394 * This property is uncacheable. 395 * 396 * @var array 397 */ 398 protected $temporary = []; 399 400 /** 401 * Tracks if the form has finished validation. 402 * 403 * This property is uncacheable. 404 * 405 * @var bool 406 */ 407 protected $validation_complete = FALSE; 408 409 /** 410 * Contains errors for this form. 411 * 412 * This property is uncacheable. 413 * 414 * @var array 415 */ 416 protected $errors = []; 417 418 /** 419 * Stores which errors should be limited during validation. 420 * 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. 427 * 428 * This property is uncacheable. 429 * 430 * @var array|null 431 */ 432 protected $limit_validation_errors; 433 434 /** 435 * Stores the gathered validation handlers. 436 * 437 * This property is uncacheable. 438 * 439 * @var array 440 */ 441 protected $validate_handlers = []; 442 443 /** 444 * Stores the gathered submission handlers. 445 * 446 * This property is uncacheable. 447 * 448 * @var array 449 */ 450 protected $submit_handlers = []; 451 452 /** 453 * {@inheritdoc} 454 */ 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; 459 } 460 else { 461 $this->set($key, $value); 462 } 463 } 464 return $this; 465 } 466 467 /** 468 * {@inheritdoc} 469 */ 470 public function setAlwaysProcess($always_process = TRUE) { 471 $this->always_process = (bool) $always_process; 472 return $this; 473 } 474 475 /** 476 * {@inheritdoc} 477 */ 478 public function getAlwaysProcess() { 479 return $this->always_process; 480 } 481 482 /** 483 * {@inheritdoc} 484 */ 485 public function setButtons(array $buttons) { 486 $this->buttons = $buttons; 487 return $this; 488 } 489 490 /** 491 * {@inheritdoc} 492 */ 493 public function getButtons() { 494 return $this->buttons; 495 } 496 497 /** 498 * {@inheritdoc} 499 */ 500 public function setCached($cache = TRUE) { 501 // Persisting $form_state is a side-effect disallowed during a "safe" HTTP 502 // method. 503 if ($cache && $this->isRequestMethodSafe()) { 504 throw new \LogicException(sprintf('Form state caching on %s requests is not allowed.', $this->requestMethod)); 505 } 506 507 $this->cache = (bool) $cache; 508 return $this; 509 } 510 511 /** 512 * {@inheritdoc} 513 */ 514 public function isCached() { 515 return empty($this->no_cache) && $this->cache; 516 } 517 518 /** 519 * {@inheritdoc} 520 */ 521 public function disableCache() { 522 $this->no_cache = TRUE; 523 return $this; 524 } 525 526 /** 527 * {@inheritdoc} 528 */ 529 public function setExecuted() { 530 $this->executed = TRUE; 531 return $this; 532 } 533 534 /** 535 * {@inheritdoc} 536 */ 537 public function isExecuted() { 538 return $this->executed; 539 } 540 541 /** 542 * {@inheritdoc} 543 */ 544 public function setGroups(array $groups) { 545 $this->groups = $groups; 546 return $this; 547 } 548 549 /** 550 * {@inheritdoc} 551 */ 552 public function &getGroups() { 553 return $this->groups; 554 } 555 556 /** 557 * {@inheritdoc} 558 */ 559 public function setHasFileElement($has_file_element = TRUE) { 560 $this->has_file_element = (bool) $has_file_element; 561 return $this; 562 } 563 564 /** 565 * {@inheritdoc} 566 */ 567 public function hasFileElement() { 568 return $this->has_file_element; 569 } 570 571 /** 572 * {@inheritdoc} 573 */ 574 public function setLimitValidationErrors($limit_validation_errors) { 575 $this->limit_validation_errors = $limit_validation_errors; 576 return $this; 577 } 578 579 /** 580 * {@inheritdoc} 581 */ 582 public function getLimitValidationErrors() { 583 return $this->limit_validation_errors; 584 } 585 586 /** 587 * {@inheritdoc} 588 */ 589 public function setMethod($method) { 590 $this->method = strtoupper($method); 591 return $this; 592 } 593 594 /** 595 * {@inheritdoc} 596 */ 597 public function isMethodType($method_type) { 598 return $this->method === strtoupper($method_type); 599 } 600 601 /** 602 * {@inheritdoc} 603 */ 604 public function setRequestMethod($method) { 605 $this->requestMethod = strtoupper($method); 606 return $this; 607 } 608 609 /** 610 * Checks whether the request method is a "safe" HTTP method. 611 * 612 * Link below defines GET and HEAD as "safe" methods, meaning they SHOULD NOT 613 * have side-effects, such as persisting $form_state changes. 614 * 615 * @return bool 616 * 617 * @see \Symfony\Component\HttpFoundation\Request::isMethodSafe() 618 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 619 */ 620 protected function isRequestMethodSafe() { 621 return in_array($this->requestMethod, ['GET', 'HEAD']); 622 } 623 624 /** 625 * {@inheritdoc} 626 */ 627 public function setValidationEnforced($must_validate = TRUE) { 628 $this->must_validate = (bool) $must_validate; 629 return $this; 630 } 631 632 /** 633 * {@inheritdoc} 634 */ 635 public function isValidationEnforced() { 636 return $this->must_validate; 637 } 638 639 /** 640 * {@inheritdoc} 641 */ 642 public function disableRedirect($no_redirect = TRUE) { 643 $this->no_redirect = (bool) $no_redirect; 644 return $this; 645 } 646 647 /** 648 * {@inheritdoc} 649 */ 650 public function isRedirectDisabled() { 651 return $this->no_redirect; 652 } 653 654 /** 655 * {@inheritdoc} 656 */ 657 public function setProcessInput($process_input = TRUE) { 658 $this->process_input = (bool) $process_input; 659 return $this; 660 } 661 662 /** 663 * {@inheritdoc} 664 */ 665 public function isProcessingInput() { 666 return $this->process_input; 667 } 668 669 /** 670 * {@inheritdoc} 671 */ 672 public function setProgrammed($programmed = TRUE) { 673 $this->programmed = (bool) $programmed; 674 return $this; 675 } 676 677 /** 678 * {@inheritdoc} 679 */ 680 public function isProgrammed() { 681 return $this->programmed; 682 } 683 684 /** 685 * {@inheritdoc} 686 */ 687 public function setProgrammedBypassAccessCheck($programmed_bypass_access_check = TRUE) { 688 $this->programmed_bypass_access_check = (bool) $programmed_bypass_access_check; 689 return $this; 690 } 691 692 /** 693 * {@inheritdoc} 694 */ 695 public function isBypassingProgrammedAccessChecks() { 696 return $this->programmed_bypass_access_check; 697 } 698 699 /** 700 * {@inheritdoc} 701 */ 702 public function setRebuildInfo(array $rebuild_info) { 703 $this->rebuild_info = $rebuild_info; 704 return $this; 705 } 706 707 /** 708 * {@inheritdoc} 709 */ 710 public function getRebuildInfo() { 711 return $this->rebuild_info; 712 } 713 714 /** 715 * {@inheritdoc} 716 */ 717 public function addRebuildInfo($property, $value) { 718 $rebuild_info = $this->getRebuildInfo(); 719 $rebuild_info[$property] = $value; 720 $this->setRebuildInfo($rebuild_info); 721 return $this; 722 } 723 724 /** 725 * {@inheritdoc} 726 */ 727 public function setStorage(array $storage) { 728 $this->storage = $storage; 729 return $this; 730 } 731 732 /** 733 * {@inheritdoc} 734 */ 735 public function &getStorage() { 736 return $this->storage; 737 } 738 739 /** 740 * {@inheritdoc} 741 */ 742 public function setSubmitHandlers(array $submit_handlers) { 743 $this->submit_handlers = $submit_handlers; 744 return $this; 745 } 746 747 /** 748 * {@inheritdoc} 749 */ 750 public function getSubmitHandlers() { 751 return $this->submit_handlers; 752 } 753 754 /** 755 * {@inheritdoc} 756 */ 757 public function setSubmitted() { 758 $this->submitted = TRUE; 759 return $this; 760 } 761 762 /** 763 * {@inheritdoc} 764 */ 765 public function isSubmitted() { 766 return $this->submitted; 767 } 768 769 /** 770 * {@inheritdoc} 771 */ 772 public function setTemporary(array $temporary) { 773 $this->temporary = $temporary; 774 return $this; 775 } 776 777 /** 778 * {@inheritdoc} 779 */ 780 public function getTemporary() { 781 return $this->temporary; 782 } 783 784 /** 785 * {@inheritdoc} 786 */ 787 public function &getTemporaryValue($key) { 788 $value = &NestedArray::getValue($this->temporary, (array) $key); 789 return $value; 790 } 791 792 /** 793 * {@inheritdoc} 794 */ 795 public function setTemporaryValue($key, $value) { 796 NestedArray::setValue($this->temporary, (array) $key, $value, TRUE); 797 return $this; 798 } 799 800 /** 801 * {@inheritdoc} 802 */ 803 public function hasTemporaryValue($key) { 804 $exists = NULL; 805 NestedArray::getValue($this->temporary, (array) $key, $exists); 806 return $exists; 807 } 808 809 /** 810 * {@inheritdoc} 811 */ 812 public function setTriggeringElement($triggering_element) { 813 $this->triggering_element = $triggering_element; 814 return $this; 815 } 816 817 /** 818 * {@inheritdoc} 819 */ 820 public function &getTriggeringElement() { 821 return $this->triggering_element; 822 } 823 824 /** 825 * {@inheritdoc} 826 */ 827 public function setValidateHandlers(array $validate_handlers) { 828 $this->validate_handlers = $validate_handlers; 829 return $this; 830 } 831 832 /** 833 * {@inheritdoc} 834 */ 835 public function getValidateHandlers() { 836 return $this->validate_handlers; 837 } 838 839 /** 840 * {@inheritdoc} 841 */ 842 public function setValidationComplete($validation_complete = TRUE) { 843 $this->validation_complete = (bool) $validation_complete; 844 return $this; 845 } 846 847 /** 848 * {@inheritdoc} 849 */ 850 public function isValidationComplete() { 851 return $this->validation_complete; 852 } 853 854 /** 855 * {@inheritdoc} 856 */ 857 public function loadInclude($module, $type, $name = NULL) { 858 if (!isset($name)) { 859 $name = $module; 860 } 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"] = [ 866 'type' => $type, 867 'module' => $module, 868 'name' => $name, 869 ]; 870 $this->setBuildInfo($build_info); 871 return $result; 872 } 873 } 874 return FALSE; 875 } 876 877 /** 878 * {@inheritdoc} 879 */ 880 public function getCacheableArray() { 881 return [ 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, 893 ]; 894 } 895 896 /** 897 * {@inheritdoc} 898 */ 899 public function setCompleteForm(array &$complete_form) { 900 $this->complete_form = &$complete_form; 901 return $this; 902 } 903 904 /** 905 * {@inheritdoc} 906 */ 907 public function &getCompleteForm() { 908 return $this->complete_form; 909 } 910 911 /** 912 * {@inheritdoc} 913 */ 914 public function &get($property) { 915 $value = &NestedArray::getValue($this->storage, (array) $property); 916 return $value; 917 } 918 919 /** 920 * {@inheritdoc} 921 */ 922 public function set($property, $value) { 923 NestedArray::setValue($this->storage, (array) $property, $value, TRUE); 924 return $this; 925 } 926 927 /** 928 * {@inheritdoc} 929 */ 930 public function has($property) { 931 $exists = NULL; 932 NestedArray::getValue($this->storage, (array) $property, $exists); 933 return $exists; 934 } 935 936 /** 937 * {@inheritdoc} 938 */ 939 public function setBuildInfo(array $build_info) { 940 $this->build_info = $build_info; 941 return $this; 942 } 943 944 /** 945 * {@inheritdoc} 946 */ 947 public function getBuildInfo() { 948 return $this->build_info; 949 } 950 951 /** 952 * {@inheritdoc} 953 */ 954 public function addBuildInfo($property, $value) { 955 $build_info = $this->getBuildInfo(); 956 $build_info[$property] = $value; 957 $this->setBuildInfo($build_info); 958 return $this; 959 } 960 961 /** 962 * {@inheritdoc} 963 */ 964 public function &getUserInput() { 965 return $this->input; 966 } 967 968 /** 969 * {@inheritdoc} 970 */ 971 public function setUserInput(array $user_input) { 972 $this->input = $user_input; 973 return $this; 974 } 975 976 /** 977 * {@inheritdoc} 978 */ 979 public function &getValues() { 980 return $this->values; 981 } 982 983 /** 984 * {@inheritdoc} 985 */ 986 public function setResponse(Response $response) { 987 $this->response = $response; 988 return $this; 989 } 990 991 /** 992 * {@inheritdoc} 993 */ 994 public function getResponse() { 995 return $this->response; 996 } 997 998 /** 999 * {@inheritdoc} 1000 */ 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); 1004 } 1005 1006 /** 1007 * {@inheritdoc} 1008 */ 1009 public function setRedirectUrl(Url $url) { 1010 $this->redirect = $url; 1011 return $this; 1012 } 1013 1014 /** 1015 * {@inheritdoc} 1016 */ 1017 public function getRedirect() { 1018 // Skip redirection for form submissions invoked via 1019 // \Drupal\Core\Form\FormBuilderInterface::submitForm(). 1020 if ($this->isProgrammed()) { 1021 return FALSE; 1022 } 1023 // Skip redirection if rebuild is activated. 1024 if ($this->isRebuilding()) { 1025 return FALSE; 1026 } 1027 // Skip redirection if it was explicitly disallowed. 1028 if ($this->isRedirectDisabled()) { 1029 return FALSE; 1030 } 1031 1032 return $this->redirect; 1033 } 1034 1035 /** 1036 * Sets the global status of errors. 1037 * 1038 * @param bool $errors 1039 * TRUE if any form has any errors, FALSE otherwise. 1040 */ 1041 protected static function setAnyErrors($errors = TRUE) { 1042 static::$anyErrors = $errors; 1043 } 1044 1045 /** 1046 * {@inheritdoc} 1047 */ 1048 public static function hasAnyErrors() { 1049 return static::$anyErrors; 1050 } 1051 1052 /** 1053 * {@inheritdoc} 1054 */ 1055 public function setErrorByName($name, $message = '') { 1056 if ($this->isValidationComplete()) { 1057 throw new \LogicException('Form errors cannot be set after form validation has finished.'); 1058 } 1059 1060 $errors = $this->getErrors(); 1061 if (!isset($errors[$name])) { 1062 $record = TRUE; 1063 $limit_validation_errors = $this->getLimitValidationErrors(); 1064 if ($limit_validation_errors !== NULL) { 1065 $record = FALSE; 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)) { 1075 $record = TRUE; 1076 break; 1077 } 1078 } 1079 } 1080 if ($record) { 1081 $errors[$name] = $message; 1082 $this->errors = $errors; 1083 static::setAnyErrors(); 1084 } 1085 } 1086 1087 return $this; 1088 } 1089 1090 /** 1091 * {@inheritdoc} 1092 */ 1093 public function setError(array &$element, $message = '') { 1094 $this->setErrorByName(implode('][', $element['#parents']), $message); 1095 return $this; 1096 } 1097 1098 /** 1099 * {@inheritdoc} 1100 */ 1101 public function clearErrors() { 1102 $this->errors = []; 1103 static::setAnyErrors(FALSE); 1104 } 1105 1106 /** 1107 * {@inheritdoc} 1108 */ 1109 public function getError(array $element) { 1110 if ($errors = $this->getErrors()) { 1111 $parents = []; 1112 foreach ($element['#parents'] as $parent) { 1113 $parents[] = $parent; 1114 $key = implode('][', $parents); 1115 if (isset($errors[$key])) { 1116 return $errors[$key]; 1117 } 1118 } 1119 } 1120 } 1121 1122 /** 1123 * {@inheritdoc} 1124 */ 1125 public function getErrors() { 1126 return $this->errors; 1127 } 1128 1129 /** 1130 * {@inheritdoc} 1131 */ 1132 public function setRebuild($rebuild = TRUE) { 1133 $this->rebuild = $rebuild; 1134 return $this; 1135 } 1136 1137 /** 1138 * {@inheritdoc} 1139 */ 1140 public function isRebuilding() { 1141 return $this->rebuild; 1142 } 1143 1144 /** 1145 * {@inheritdoc} 1146 */ 1147 public function prepareCallback($callback) { 1148 if (is_string($callback) && substr($callback, 0, 2) == '::') { 1149 $callback = [$this->getFormObject(), substr($callback, 2)]; 1150 } 1151 return $callback; 1152 } 1153 1154 /** 1155 * {@inheritdoc} 1156 */ 1157 public function setFormObject(FormInterface $form_object) { 1158 $this->addBuildInfo('callback_object', $form_object); 1159 return $this; 1160 } 1161 1162 /** 1163 * {@inheritdoc} 1164 */ 1165 public function getFormObject() { 1166 return $this->getBuildInfo()['callback_object']; 1167 } 1168 1169 /** 1170 * {@inheritdoc} 1171 */ 1172 public function getCleanValueKeys() { 1173 return $this->cleanValueKeys; 1174 } 1175 1176 /** 1177 * {@inheritdoc} 1178 */ 1179 public function setCleanValueKeys(array $cleanValueKeys) { 1180 $this->cleanValueKeys = $cleanValueKeys; 1181 return $this; 1182 } 1183 1184 /** 1185 * {@inheritdoc} 1186 */ 1187 public function addCleanValueKey($cleanValueKey) { 1188 $keys = $this->getCleanValueKeys(); 1189 $this->setCleanValueKeys(array_merge((array) $keys, [$cleanValueKey])); 1190 return $this; 1191 } 1192 1193 /** 1194 * {@inheritdoc} 1195 */ 1196 public function cleanValues() { 1197 foreach ($this->getCleanValueKeys() as $value) { 1198 $this->unsetValue($value); 1199 } 1200 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: 1211 // array( 1212 // 'foo' => array( 1213 // 'bar' => array( 1214 // 'baz' => 'button_value', 1215 // ), 1216 // ), 1217 // ) 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); 1226 $key_exists = NULL; 1227 $values = &NestedArray::getValue($this->getValues(), $parents, $key_exists); 1228 if ($key_exists && is_array($values)) { 1229 unset($values[$last_parent]); 1230 } 1231 } 1232 return $this; 1233 } 1234 1235 /** 1236 * {@inheritdoc} 1237 */ 1238 public function setInvalidToken($invalid_token) { 1239 $this->invalidToken = (bool) $invalid_token; 1240 return $this; 1241 } 1242 1243 /** 1244 * {@inheritdoc} 1245 */ 1246 public function hasInvalidToken() { 1247 return $this->invalidToken; 1248 } 1249 1250 /** 1251 * Wraps ModuleHandler::loadInclude(). 1252 */ 1253 protected function moduleLoadInclude($module, $type, $name = NULL) { 1254 return \Drupal::moduleHandler()->loadInclude($module, $type, $name); 1255 } 1256 1257} 1258