FormBuilder.php 59.5 KB
Newer Older
1 2 3 4 5
<?php

namespace Drupal\Core\Form;

use Drupal\Component\Utility\Crypt;
6
use Drupal\Component\Utility\Environment;
7
use Drupal\Component\Utility\Html;
8
use Drupal\Component\Utility\NestedArray;
9
use Drupal\Component\Utility\UrlHelper;
10
use Drupal\Core\Access\AccessResultInterface;
11
use Drupal\Core\Access\CsrfTokenGenerator;
12
use Drupal\Core\DependencyInjection\ClassResolverInterface;
13
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
14
use Drupal\Core\Extension\ModuleHandlerInterface;
15
use Drupal\Core\Form\Exception\BrokenPostRequestException;
16
use Drupal\Core\Render\Element;
17
use Drupal\Core\Render\ElementInfoManagerInterface;
18
use Drupal\Core\Theme\ThemeManagerInterface;
19
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
20
use Symfony\Component\HttpFoundation\FileBag;
21
use Symfony\Component\HttpFoundation\RequestStack;
22 23 24 25
use Symfony\Component\HttpFoundation\Response;

/**
 * Provides form building and processing.
26 27
 *
 * @ingroup form_api
28
 */
29
class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormSubmitterInterface, FormCacheInterface {
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
46
   * The request stack.
47
   *
48
   * @var \Symfony\Component\HttpFoundation\RequestStack
49
   */
50
  protected $requestStack;
51

52 53 54 55 56 57 58
  /**
   * The element info manager.
   *
   * @var \Drupal\Core\Render\ElementInfoManagerInterface
   */
  protected $elementInfo;

59 60 61 62 63 64 65
  /**
   * The CSRF token generator to validate the form token.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $csrfToken;

66 67 68 69 70 71 72
  /**
   * The class resolver.
   *
   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
   */
  protected $classResolver;

73 74 75 76 77 78 79
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

80 81 82 83 84 85 86
  /**
   * The theme manager.
   *
   * @var \Drupal\Core\Theme\ThemeManagerInterface
   */
  protected $themeManager;

87
  /**
88 89
   * The form validator.
   *
90
   * @var \Drupal\Core\Form\FormValidatorInterface
91
   */
92
  protected $formValidator;
93

94
  /**
95 96
   * The form submitter.
   *
97 98 99 100
   * @var \Drupal\Core\Form\FormSubmitterInterface
   */
  protected $formSubmitter;

101 102 103 104 105 106 107
  /**
   * The form cache.
   *
   * @var \Drupal\Core\Form\FormCacheInterface
   */
  protected $formCache;

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
  /**
   * Defines element value callables which are safe to run even when the form
   * state has an invalid CSRF token.
   *
   * Excluded from this list on purpose:
   *  - Drupal\file\Element\ManagedFile::valueCallback
   *  - Drupal\Core\Datetime\Element\Datelist::valueCallback
   *  - Drupal\Core\Datetime\Element\Datetime::valueCallback
   *  - Drupal\Core\Render\Element\ImageButton::valueCallback
   *  - Drupal\file\Plugin\Field\FieldWidget\FileWidget::value
   *  - color_palette_color_value
   *
   * @var array
   */
  protected $safeCoreValueCallables = [
    'Drupal\Core\Render\Element\Checkbox::valueCallback',
    'Drupal\Core\Render\Element\Checkboxes::valueCallback',
    'Drupal\Core\Render\Element\Email::valueCallback',
    'Drupal\Core\Render\Element\FormElement::valueCallback',
    'Drupal\Core\Render\Element\MachineName::valueCallback',
    'Drupal\Core\Render\Element\Number::valueCallback',
    'Drupal\Core\Render\Element\PathElement::valueCallback',
    'Drupal\Core\Render\Element\Password::valueCallback',
    'Drupal\Core\Render\Element\PasswordConfirm::valueCallback',
    'Drupal\Core\Render\Element\Radio::valueCallback',
    'Drupal\Core\Render\Element\Radios::valueCallback',
    'Drupal\Core\Render\Element\Range::valueCallback',
    'Drupal\Core\Render\Element\Search::valueCallback',
    'Drupal\Core\Render\Element\Select::valueCallback',
    'Drupal\Core\Render\Element\Tableselect::valueCallback',
    'Drupal\Core\Render\Element\Table::valueCallback',
    'Drupal\Core\Render\Element\Tel::valueCallback',
    'Drupal\Core\Render\Element\Textarea::valueCallback',
    'Drupal\Core\Render\Element\Textfield::valueCallback',
    'Drupal\Core\Render\Element\Token::valueCallback',
    'Drupal\Core\Render\Element\Url::valueCallback',
    'Drupal\Core\Render\Element\Weight::valueCallback',
  ];

147 148 149
  /**
   * Constructs a new FormBuilder.
   *
150 151
   * @param \Drupal\Core\Form\FormValidatorInterface $form_validator
   *   The form validator.
152 153
   * @param \Drupal\Core\Form\FormSubmitterInterface $form_submitter
   *   The form submission processor.
154
   * @param \Drupal\Core\Form\FormCacheInterface $form_cache
155
   *   The form cache.
156 157 158 159
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
160 161
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
162 163
   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
   *   The class resolver.
164 165
   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
   *   The element info manager.
166 167
   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
   *   The theme manager.
168 169 170
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
   *   The CSRF token generator.
   */
171
  public function __construct(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) {
172
    $this->formValidator = $form_validator;
173
    $this->formSubmitter = $form_submitter;
174
    $this->formCache = $form_cache;
175 176
    $this->moduleHandler = $module_handler;
    $this->eventDispatcher = $event_dispatcher;
177
    $this->requestStack = $request_stack;
178
    $this->classResolver = $class_resolver;
179
    $this->elementInfo = $element_info;
180
    $this->csrfToken = $csrf_token;
181
    $this->themeManager = $theme_manager;
182 183 184 185 186
  }

  /**
   * {@inheritdoc}
   */
187
  public function getFormId($form_arg, FormStateInterface &$form_state) {
188 189
    // If the $form_arg is the name of a class, instantiate it. Don't allow
    // arbitrary strings to be passed to the class resolver.
190
    if (is_string($form_arg) && class_exists($form_arg)) {
191
      $form_arg = $this->classResolver->getInstanceFromDefinition($form_arg);
192
    }
193

194
    if (!is_object($form_arg) || !($form_arg instanceof FormInterface)) {
195
      throw new \InvalidArgumentException("The form argument $form_arg is not a valid form.");
196 197
    }

198
    // Add the $form_arg as the callback object and determine the form ID.
199
    $form_state->setFormObject($form_arg);
200
    if ($form_arg instanceof BaseFormIdInterface) {
201
      $form_state->addBuildInfo('base_form_id', $form_arg->getBaseFormId());
202 203
    }
    return $form_arg->getFormId();
204 205 206 207 208 209
  }

  /**
   * {@inheritdoc}
   */
  public function getForm($form_arg) {
210
    $form_state = new FormState();
211 212 213

    $args = func_get_args();
    // Remove $form_arg from the arguments.
214
    unset($args[0]);
215
    $form_state->addBuildInfo('args', array_values($args));
216

217
    return $this->buildForm($form_arg, $form_state);
218 219 220 221 222
  }

  /**
   * {@inheritdoc}
   */
223
  public function buildForm($form_arg, FormStateInterface &$form_state) {
224
    // Ensure the form ID is prepared.
225
    $form_id = $this->getFormId($form_arg, $form_state);
226

227 228 229 230 231 232 233 234 235 236 237 238 239 240
    $request = $this->requestStack->getCurrentRequest();

    // Inform $form_state about the request method that's building it, so that
    // it can prevent persisting state changes during HTTP methods for which
    // that is disallowed by HTTP: GET and HEAD.
    $form_state->setRequestMethod($request->getMethod());

    // Initialize the form's user input. The user input should include only the
    // input meant to be treated as part of what is submitted to the form, so
    // we base it on the form's method rather than the request's method. For
    // example, when someone does a GET request for
    // /node/add/article?destination=foo, which is a form that expects its
    // submission method to be POST, the user input during the GET request
    // should be initialized to empty rather than to ['destination' => 'foo'].
241 242
    $input = $form_state->getUserInput();
    if (!isset($input)) {
243
      $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all();
244
      $form_state->setUserInput($input);
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    }

    if (isset($_SESSION['batch_form_state'])) {
      // We've been redirected here after a batch processing. The form has
      // already been processed, but needs to be rebuilt. See _batch_finished().
      $form_state = $_SESSION['batch_form_state'];
      unset($_SESSION['batch_form_state']);
      return $this->rebuildForm($form_id, $form_state);
    }

    // If the incoming input contains a form_build_id, we'll check the cache for
    // a copy of the form in question. If it's there, we don't have to rebuild
    // the form to proceed. In addition, if there is stored form_state data from
    // a previous step, we'll retrieve it so it can be passed on to the form
    // processing code.
260
    $check_cache = isset($input['form_id']) && $input['form_id'] == $form_id && !empty($input['form_build_id']);
261
    if ($check_cache) {
262
      $form = $this->getCache($input['form_build_id'], $form_state);
263 264 265 266 267 268 269 270 271 272
    }

    // If the previous bit of code didn't result in a populated $form object, we
    // are hitting the form for the first time and we need to build it from
    // scratch.
    if (!isset($form)) {
      // If we attempted to serve the form from cache, uncacheable $form_state
      // keys need to be removed after retrieving and preparing the form, except
      // any that were already set prior to retrieving the form.
      if ($check_cache) {
273
        $form_state_before_retrieval = clone $form_state;
274 275 276 277 278
      }

      $form = $this->retrieveForm($form_id, $form_state);
      $this->prepareForm($form_id, $form, $form_state);

279 280
      // self::setCache() removes uncacheable $form_state keys (see properties
      // in \Drupal\Core\Form\FormState) in order for multi-step forms to work
281
      // properly. This means that form processing logic for single-step forms
282
      // using $form_state->isCached() may depend on data stored in those keys
283 284 285
      // during self::retrieveForm()/self::prepareForm(), but form processing
      // should not depend on whether the form is cached or not, so $form_state
      // is adjusted to match what it would be after a
286 287 288 289 290 291 292 293
      // self::setCache()/self::getCache() sequence. These exceptions are
      // allowed to survive here:
      // - always_process: Does not make sense in conjunction with form caching
      //   in the first place, since passing form_build_id as a GET parameter is
      //   not desired.
      // - temporary: Any assigned data is expected to survives within the same
      //   page request.
      if ($check_cache) {
294 295 296
        $cache_form_state = $form_state->getCacheableArray();
        $cache_form_state['always_process'] = $form_state->getAlwaysProcess();
        $cache_form_state['temporary'] = $form_state->getTemporary();
297 298
        $form_state = $form_state_before_retrieval;
        $form_state->setFormState($cache_form_state);
299 300 301
      }
    }

302 303 304 305 306 307
    // If this form is an AJAX request, disable all form redirects.
    $request = $this->requestStack->getCurrentRequest();
    if ($ajax_form_request = $request->query->has(static::AJAX_FORM_REQUEST)) {
      $form_state->disableRedirect();
    }

308 309 310
    // Now that we have a constructed form, process it. This is where:
    // - Element #process functions get called to further refine $form.
    // - User input, if any, gets incorporated in the #value property of the
311
    //   corresponding elements and into $form_state->getValues().
312 313 314 315 316 317 318 319 320
    // - Validation and submission handlers are called.
    // - If this submission is part of a multistep workflow, the form is rebuilt
    //   to contain the information of the next step.
    // - If necessary, the form and form state are cached or re-cached, so that
    //   appropriate information persists to the next page request.
    // All of the handlers in the pipeline receive $form_state by reference and
    // can use it to know or update information about the state of the form.
    $response = $this->processForm($form_id, $form, $form_state);

321 322 323
    // In case the post request exceeds the configured allowed size
    // (post_max_size), the post request is potentially broken. Add some
    // protection against that and at the same time have a nice error message.
324
    if ($ajax_form_request && !$request->request->has('form_id')) {
325 326 327
      throw new BrokenPostRequestException($this->getFileUploadMaxSize());
    }

328 329 330 331 332 333 334
    // After processing the form, if this is an AJAX form request, interrupt
    // form rendering and return by throwing an exception that contains the
    // processed form and form state. This exception will be caught by
    // \Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber::onException() and
    // then passed through
    // \Drupal\Core\Form\FormAjaxResponseBuilderInterface::buildResponse() to
    // build a proper AJAX response.
335 336 337
    // Only do this when the form ID matches, since there is no guarantee from
    // $ajax_form_request that it's an AJAX request for this particular form.
    if ($ajax_form_request && $form_state->isProcessingInput() && $request->request->get('form_id') == $form_id) {
338 339 340
      throw new FormAjaxException($form, $form_state);
    }

341 342 343 344 345 346 347 348 349
    // If the form returns a response, skip subsequent page construction by
    // throwing an exception.
    // @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber
    //
    // @todo Exceptions should not be used for code flow control. However, the
    //   Form API does not integrate with the HTTP Kernel based architecture of
    //   Drupal 8. In order to resolve this issue properly it is necessary to
    //   completely separate form submission from rendering.
    //   @see https://www.drupal.org/node/2367555
350
    if ($response instanceof Response) {
351
      throw new EnforcedResponseException($response);
352 353
    }

354 355
    // If this was a successful submission of a single-step form or the last
    // step of a multi-step form, then self::processForm() issued a redirect to
356 357 358 359 360 361 362 363 364 365 366 367
    // another page, or back to this page, but as a new request. Therefore, if
    // we're here, it means that this is either a form being viewed initially
    // before any user input, or there was a validation error requiring the form
    // to be re-displayed, or we're in a multi-step workflow and need to display
    // the form's next step. In any case, we have what we need in $form, and can
    // return it for rendering.
    return $form;
  }

  /**
   * {@inheritdoc}
   */
368
  public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) {
369
    $form = $this->retrieveForm($form_id, $form_state);
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

    // Only GET and POST are valid form methods. If the form receives its input
    // via POST, then $form_state must be persisted when it is rebuilt between
    // submissions. If the form receives its input via GET, then persisting
    // state is forbidden by $form_state->setCached(), and the form must use
    // the URL itself to transfer its state across steps. Although $form_state
    // throws an exception based on the request method rather than the form's
    // method, we base the decision to cache on the form method, because:
    // - It's the form method that defines what the form needs to do to manage
    //   its state.
    // - rebuildForm() should only be called after successful input processing,
    //   which means the request method matches the form method, and if not,
    //   there's some other error, so it's ok if an exception is thrown.
    if ($form_state->isMethodType('POST')) {
      $form_state->setCached();
    }
386 387

    // If only parts of the form will be returned to the browser (e.g., Ajax or
388 389
    // RIA clients), or if the form already had a new build ID regenerated when
    // it was retrieved from the form cache, reuse the existing #build_id.
390 391 392 393
    // Otherwise, a new #build_id is generated, to not clobber the previous
    // build's data in the form cache; also allowing the user to go back to an
    // earlier build, make changes, and re-submit.
    // @see self::prepareForm()
394
    $rebuild_info = $form_state->getRebuildInfo();
395 396 397
    $enforce_old_build_id = isset($old_form['#build_id']) && !empty($rebuild_info['copy']['#build_id']);
    $old_form_is_mutable_copy = isset($old_form['#build_id_old']);
    if ($enforce_old_build_id || $old_form_is_mutable_copy) {
398
      $form['#build_id'] = $old_form['#build_id'];
399 400 401
      if ($old_form_is_mutable_copy) {
        $form['#build_id_old'] = $old_form['#build_id_old'];
      }
402 403
    }
    else {
404 405 406
      if (isset($old_form['#build_id'])) {
        $form['#build_id_old'] = $old_form['#build_id'];
      }
407
      $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
408 409
    }

410 411 412
    // #action defaults to $request->getRequestUri(), but in case of Ajax and
    // other partial rebuilds, the form is submitted to an alternate URL, and
    // the original #action needs to be retained.
413
    if (isset($old_form['#action']) && !empty($rebuild_info['copy']['#action'])) {
414 415 416 417 418 419 420 421 422
      $form['#action'] = $old_form['#action'];
    }

    $this->prepareForm($form_id, $form, $form_state);

    // Caching is normally done in self::processForm(), but what needs to be
    // cached is the $form structure before it passes through
    // self::doBuildForm(), so we need to do it here.
    // @todo For Drupal 8, find a way to avoid this code duplication.
423
    if ($form_state->isCached()) {
424 425 426 427 428
      $this->setCache($form['#build_id'], $form, $form_state);
    }

    // Clear out all group associations as these might be different when
    // re-rendering the form.
429
    $form_state->setGroups([]);
430 431 432 433 434 435 436 437

    // Return a fully built form that is ready for rendering.
    return $this->doBuildForm($form_id, $form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
438 439
  public function getCache($form_build_id, FormStateInterface $form_state) {
    return $this->formCache->getCache($form_build_id, $form_state);
440 441 442 443 444
  }

  /**
   * {@inheritdoc}
   */
445
  public function setCache($form_build_id, $form, FormStateInterface $form_state) {
446
    $this->formCache->setCache($form_build_id, $form, $form_state);
447 448
  }

449 450 451 452 453 454 455
  /**
   * {@inheritdoc}
   */
  public function deleteCache($form_build_id) {
    $this->formCache->deleteCache($form_build_id);
  }

456 457 458
  /**
   * {@inheritdoc}
   */
459
  public function submitForm($form_arg, FormStateInterface &$form_state) {
460 461
    $build_info = $form_state->getBuildInfo();
    if (empty($build_info['args'])) {
462
      $args = func_get_args();
463 464
      // Remove $form and $form_state from the arguments.
      unset($args[0], $args[1]);
465
      $form_state->addBuildInfo('args', array_values($args));
466 467
    }

468
    // Populate FormState::$input with the submitted values before retrieving
469 470 471
    // the form, to be consistent with what self::buildForm() does for
    // non-programmatic submissions (form builder functions may expect it to be
    // there).
472
    $form_state->setUserInput($form_state->getValues());
473

474
    $form_state->setProgrammed();
475 476 477 478

    $form_id = $this->getFormId($form_arg, $form_state);
    $form = $this->retrieveForm($form_id, $form_state);
    // Programmed forms are always submitted.
479
    $form_state->setSubmitted();
480 481

    // Reset form validation.
482
    $form_state->setValidationEnforced();
483
    $form_state->clearErrors();
484 485 486 487 488 489 490 491

    $this->prepareForm($form_id, $form, $form_state);
    $this->processForm($form_id, $form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
492
  public function retrieveForm($form_id, FormStateInterface &$form_state) {
493
    // Record the $form_id.
494
    $form_state->addBuildInfo('form_id', $form_id);
495 496 497 498

    // We save two copies of the incoming arguments: one for modules to use
    // when mapping form ids to constructor functions, and another to pass to
    // the constructor function itself.
499 500
    $build_info = $form_state->getBuildInfo();
    $args = $build_info['args'];
501

502
    $callback = [$form_state->getFormObject(), 'buildForm'];
503

504
    $form = [];
505 506 507
    // Assign a default CSS class name based on $form_id.
    // This happens here and not in self::prepareForm() in order to allow the
    // form constructor function to override or remove the default class.
508
    $form['#attributes']['class'][] = Html::getClass($form_id);
509
    // Same for the base form ID, if any.
510 511
    if (isset($build_info['base_form_id'])) {
      $form['#attributes']['class'][] = Html::getClass($build_info['base_form_id']);
512 513 514 515 516
    }

    // We need to pass $form_state by reference in order for forms to modify it,
    // since call_user_func_array() requires that referenced variables are
    // passed explicitly.
517
    $args = array_merge([$form, &$form_state], $args);
518 519

    $form = call_user_func_array($callback, $args);
520 521 522 523 524 525 526 527
    // If the form returns a response, skip subsequent page construction by
    // throwing an exception.
    // @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber
    //
    // @todo Exceptions should not be used for code flow control. However, the
    //   Form API currently allows any form builder functions to return a
    //   response.
    //   @see https://www.drupal.org/node/2363189
528
    if ($form instanceof Response) {
529
      throw new EnforcedResponseException($form);
530 531 532 533 534 535 536 537 538
    }
    $form['#form_id'] = $form_id;

    return $form;
  }

  /**
   * {@inheritdoc}
   */
539
  public function processForm($form_id, &$form, FormStateInterface &$form_state) {
540
    $form_state->setValues([]);
541

542
    // With GET, these forms are always submitted if requested.
543
    if ($form_state->isMethodType('get') && $form_state->getAlwaysProcess()) {
544
      $input = $form_state->getUserInput();
545 546
      if (!isset($input['form_build_id'])) {
        $input['form_build_id'] = $form['#build_id'];
547
      }
548 549
      if (!isset($input['form_id'])) {
        $input['form_id'] = $form_id;
550
      }
551 552
      if (!isset($input['form_token']) && isset($form['#token'])) {
        $input['form_token'] = $this->csrfToken->get($form['#token']);
553
      }
554
      $form_state->setUserInput($input);
555 556 557 558
    }

    // self::doBuildForm() finishes building the form by calling element
    // #process functions and mapping user input, if any, to #value properties,
559 560
    // and also storing the values in $form_state->getValues(). We need to
    // retain the unprocessed $form in case it needs to be cached.
561 562 563 564
    $unprocessed_form = $form;
    $form = $this->doBuildForm($form_id, $form, $form_state);

    // Only process the input if we have a correct form submission.
565
    if ($form_state->isProcessingInput()) {
566 567 568 569 570 571
      // Form values for programmed form submissions typically do not include a
      // value for the submit button. But without a triggering element, a
      // potentially existing #limit_validation_errors property on the primary
      // submit button is not taken account. Therefore, check whether there is
      // exactly one submit button in the form, and if so, automatically use it
      // as triggering_element.
572 573 574
      $buttons = $form_state->getButtons();
      if ($form_state->isProgrammed() && !$form_state->getTriggeringElement() && count($buttons) == 1) {
        $form_state->setTriggeringElement(reset($buttons));
575
      }
576
      $this->formValidator->validateForm($form_id, $form, $form_state);
577

578 579 580 581 582
      // \Drupal\Component\Utility\Html::getUniqueId() maintains a cache of
      // element IDs it has seen, so it can prevent duplicates. We want to be
      // sure we reset that cache when a form is processed, so scenarios that
      // result in the form being built behind the scenes and again for the
      // browser don't increment all the element IDs needlessly.
583
      if (!FormState::hasAnyErrors()) {
584
        // In case of errors, do not break HTML IDs of other forms.
585
        Html::resetSeenIds();
586 587
      }

588
      // If there are no errors and the form is not rebuilding, submit the form.
589
      if (!$form_state->isRebuilding() && !FormState::hasAnyErrors()) {
590 591 592 593 594 595 596
        $submit_response = $this->formSubmitter->doSubmitForm($form, $form_state);
        // If this form was cached, delete it from the cache after submission.
        if ($form_state->isCached()) {
          $this->deleteCache($form['#build_id']);
        }
        // If the form submission directly returned a response, return it now.
        if ($submit_response) {
597
          return $submit_response;
598 599 600 601
        }
      }

      // Don't rebuild or cache form submissions invoked via self::submitForm().
602
      if ($form_state->isProgrammed()) {
603 604 605
        return;
      }

606 607 608 609 610 611 612 613 614 615 616
      // If $form_state->isRebuilding() has been set and input has been
      // processed without validation errors, we are in a multi-step workflow
      // that is not yet complete. A new $form needs to be constructed based on
      // the changes made to $form_state during this request. Normally, a submit
      // handler sets $form_state->isRebuilding() if a fully executed form
      // requires another step. However, for forms that have not been fully
      // executed (e.g., Ajax submissions triggered by non-buttons), there is no
      // submit handler to set $form_state->isRebuilding(). It would not make
      // sense to redisplay the identical form without an error for the user to
      // correct, so we also rebuild error-free non-executed forms, regardless
      // of $form_state->isRebuilding().
617 618 619 620
      // @todo Simplify this logic; considering Ajax and non-HTML front-ends,
      //   along with element-level #submit properties, it makes no sense to
      //   have divergent form execution based on whether the triggering element
      //   has #executes_submit_callback set to TRUE.
621
      if (($form_state->isRebuilding() || !$form_state->isExecuted()) && !FormState::hasAnyErrors()) {
622
        // Form building functions (e.g., self::handleInputElement()) may use
623
        // $form_state->isRebuilding() to determine if they are running in the
624
        // context of a rebuild, so ensure it is set.
625
        $form_state->setRebuild();
626 627 628 629 630
        $form = $this->rebuildForm($form_id, $form_state, $form);
      }
    }

    // After processing the form, the form builder or a #process callback may
631 632 633 634 635 636 637
    // have called $form_state->setCached() to indicate that the form and form
    // state shall be cached. But the form may only be cached if
    // $form_state->disableCache() is not called. Only cache $form as it was
    // prior to self::doBuildForm(), because self::doBuildForm() must run for
    // each request to accommodate new user input. Rebuilt forms are not cached
    // here, because self::rebuildForm() already takes care of that.
    if (!$form_state->isRebuilding() && $form_state->isCached()) {
638 639 640 641
      $this->setCache($form['#build_id'], $unprocessed_form, $form_state);
    }
  }

642 643 644 645 646 647 648 649 650 651 652 653 654 655
  /**
   * #lazy_builder callback; renders a form action URL.
   *
   * @return array
   *   A renderable array representing the form action.
   */
  public function renderPlaceholderFormAction() {
    return [
      '#type' => 'markup',
      '#markup' => $this->buildFormAction(),
      '#cache' => ['contexts' => ['url.path', 'url.query_args']],
    ];
  }

656 657 658 659
  /**
   * #lazy_builder callback; renders form CSRF token.
   *
   * @param string $placeholder
660 661
   *   A string containing a placeholder, matching the value of the form's
   *   #token.
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
   *
   * @return array
   *   A renderable array containing the CSRF token.
   */
  public function renderFormTokenPlaceholder($placeholder) {
    return [
      '#markup' => $this->csrfToken->get($placeholder),
      '#cache' => [
        'contexts' => [
          'session',
        ],
      ],
    ];
  }

677 678 679
  /**
   * {@inheritdoc}
   */
680
  public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
681 682 683 684
    $user = $this->currentUser();

    $form['#type'] = 'form';

685 686
    // Only update the action if it is not already set.
    if (!isset($form['#action'])) {
687 688 689 690
      // Instead of setting an actual action URL, we set the placeholder, which
      // will be replaced at the very last moment. This ensures forms with
      // dynamically generated action URLs don't have poor cacheability.
      // Use the proper API to generate the placeholder, when we have one. See
691
      // https://www.drupal.org/node/2562341. The placeholder uses a fixed string
692 693
      // that is Crypt::hashBase64('Drupal\Core\Form\FormBuilder::prepareForm');
      $placeholder = 'form_action_p_pvdeGsVG5zNF_XLGPTvYSKCf43t8qZYSwcfZl2uzM';
694 695 696 697 698

      $form['#attached']['placeholders'][$placeholder] = [
        '#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []],
      ];
      $form['#action'] = $placeholder;
699 700
    }

701
    // Fix the form method, if it is 'get' in $form_state, but not in $form.
702
    if ($form_state->isMethodType('get') && !isset($form['#method'])) {
703 704 705
      $form['#method'] = 'get';
    }

706 707 708 709 710 711 712 713 714
    // GET forms should not use a CSRF token.
    if (isset($form['#method']) && $form['#method'] === 'get') {
      // Merges in a default, this means if you've explicitly set #token to the
      // the $form_id on a GET form, which we don't recommend, it will work.
      $form += [
        '#token' => FALSE,
      ];
    }

715 716 717 718 719 720 721
    // Generate a new #build_id for this form, if none has been set already.
    // The form_build_id is used as key to cache a particular build of the form.
    // For multi-step forms, this allows the user to go back to an earlier
    // build, make changes, and re-submit.
    // @see self::buildForm()
    // @see self::rebuildForm()
    if (!isset($form['#build_id'])) {
722
      $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
723
    }
724
    $form['form_build_id'] = [
725 726 727 728 729 730 731
      '#type' => 'hidden',
      '#value' => $form['#build_id'],
      '#id' => $form['#build_id'],
      '#name' => 'form_build_id',
      // Form processing and validation requires this value, so ensure the
      // submitted form value appears literally, regardless of custom #tree
      // and #parents being set elsewhere.
732
      '#parents' => ['form_build_id'],
733 734 735 736 737 738 739 740 741 742 743
      // Prevent user agents from prefilling the build id with earlier values.
      // When the ajax command "update_build_id" is executed, the user agent
      // will assume that a user interaction changed the field. Upon a soft
      // reload of the page, the previous build id will be restored in the
      // input, causing subsequent ajax callbacks to access the wrong cached
      // form build. Setting the autocomplete attribute to "off" will tell the
      // user agent to never reuse the value.
      // @see https://www.w3.org/TR/2011/WD-html5-20110525/common-input-element-attributes.html#the-autocomplete-attribute
      '#attributes' => [
        'autocomplete' => 'off',
      ],
744
    ];
745 746 747 748 749 750 751 752 753

    // Add a token, based on either #token or form_id, to any form displayed to
    // authenticated users. This ensures that any submitted form was actually
    // requested previously by the user and protects against cross site request
    // forgeries.
    // This does not apply to programmatically submitted forms. Furthermore,
    // since tokens are session-bound and forms displayed to anonymous users are
    // very likely cached, we cannot assign a token for them.
    // During installation, there is no $user yet.
754 755 756 757 758
    // Form constructors may explicitly set #token to FALSE when cross site
    // request forgery is irrelevant to the form, such as search forms.
    if ($form_state->isProgrammed() || (isset($form['#token']) && $form['#token'] === FALSE)) {
      unset($form['#token']);
    }
759 760 761 762
    else {
      $form['#cache']['contexts'][] = 'user.roles:authenticated';
      if ($user && $user->isAuthenticated()) {
        // Generate a public token based on the form id.
763
        // Generates a placeholder based on the form ID.
764
        $placeholder = 'form_token_placeholder_' . Crypt::hashBase64($form_id);
765 766
        $form['#token'] = $placeholder;

767
        $form['form_token'] = [
768 769
          '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'),
          '#type' => 'token',
770
          '#default_value' => $placeholder,
771 772 773
          // Form processing and validation requires this value, so ensure the
          // submitted form value appears literally, regardless of custom #tree
          // and #parents being set elsewhere.
774
          '#parents' => ['form_token'],
775 776 777 778 779 780 781
          // Instead of setting an actual CSRF token, we've set the placeholder
          // in form_token's #default_value and #placeholder. These will be
          // replaced at the very last moment. This ensures forms with a CSRF
          // token don't have poor cacheability.
          '#attached' => [
            'placeholders' => [
              $placeholder => [
782 783 784
                '#lazy_builder' => ['form_builder:renderFormTokenPlaceholder', [$placeholder]],
              ],
            ],
785
          ],
786 787 788
          '#cache' => [
            'max-age' => 0,
          ],
789
        ];
790
      }
791 792 793
    }

    if (isset($form_id)) {
794
      $form['form_id'] = [
795 796
        '#type' => 'hidden',
        '#value' => $form_id,
797
        '#id' => Html::getUniqueId("edit-$form_id"),
798 799 800
        // Form processing and validation requires this value, so ensure the
        // submitted form value appears literally, regardless of custom #tree
        // and #parents being set elsewhere.
801 802
        '#parents' => ['form_id'],
      ];
803 804
    }
    if (!isset($form['#id'])) {
805
      $form['#id'] = Html::getUniqueId($form_id);
806 807 808
      // Provide a selector usable by JavaScript. As the ID is unique, its not
      // possible to rely on it in JavaScript.
      $form['#attributes']['data-drupal-selector'] = Html::getId($form_id);
809 810
    }

811
    $form += $this->elementInfo->getInfo('form');
812
    $form += ['#tree' => FALSE, '#parents' => []];
813 814
    $form['#validate'][] = '::validateForm';
    $form['#submit'][] = '::submitForm';
815

816
    $build_info = $form_state->getBuildInfo();
817
    // If no #theme has been set, automatically apply theme suggestions.
818 819 820
    // The form theme hook itself, which is rendered by form.html.twig,
    // is in #theme_wrappers. Therefore, the #theme function only has to care
    // for rendering the inner form elements, not the form itself.
821
    if (!isset($form['#theme'])) {
822
      $form['#theme'] = [$form_id];
823 824
      if (isset($build_info['base_form_id'])) {
        $form['#theme'][] = $build_info['base_form_id'];
825 826 827 828 829
      }
    }

    // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and
    // hook_form_FORM_ID_alter() implementations.
830
    $hooks = ['form'];
831 832
    if (isset($build_info['base_form_id'])) {
      $hooks[] = 'form_' . $build_info['base_form_id'];
833 834 835
    }
    $hooks[] = 'form_' . $form_id;
    $this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
836
    $this->themeManager->alter($hooks, $form, $form_state, $form_id);
837 838
  }

839 840 841 842 843 844 845 846 847
  /**
   * Builds the $form['#action'].
   *
   * @return string
   *   The URL to be used as the $form['#action'].
   */
  protected function buildFormAction() {
    // @todo Use <current> instead of the master request in
    //   https://www.drupal.org/node/2505339.
848 849 850 851 852 853 854 855
    $request = $this->requestStack->getMasterRequest();
    $request_uri = $request->getRequestUri();

    // Prevent cross site requests via the Form API by using an absolute URL
    // when the request uri starts with multiple slashes..
    if (strpos($request_uri, '//') === 0) {
      $request_uri = $request->getUri();
    }
856 857 858 859 860 861 862 863

    // @todo Remove this parsing once these are removed from the request in
    //   https://www.drupal.org/node/2504709.
    $parsed = UrlHelper::parse($request_uri);
    unset($parsed['query'][static::AJAX_FORM_REQUEST], $parsed['query'][MainContentViewSubscriber::WRAPPER_FORMAT]);
    return $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');
  }

864 865 866 867 868 869 870
  /**
   * {@inheritdoc}
   */
  public function setInvalidTokenError(FormStateInterface $form_state) {
    $this->formValidator->setInvalidTokenError($form_state);
  }

871 872 873
  /**
   * {@inheritdoc}
   */
874
  public function validateForm($form_id, &$form, FormStateInterface &$form_state) {
875
    $this->formValidator->validateForm($form_id, $form, $form_state);
876 877
  }

878 879 880
  /**
   * {@inheritdoc}
   */
881
  public function redirectForm(FormStateInterface $form_state) {
882
    return $this->formSubmitter->redirectForm($form_state);
883 884 885
  }

  /**
886
   * {@inheritdoc}
887
   */
888
  public function executeValidateHandlers(&$form, FormStateInterface &$form_state) {
889
    $this->formValidator->executeValidateHandlers($form, $form_state);
890 891 892 893 894
  }

  /**
   * {@inheritdoc}
   */
895
  public function executeSubmitHandlers(&$form, FormStateInterface &$form_state) {
896 897
    $this->formSubmitter->executeSubmitHandlers($form, $form_state);
  }
898

899 900 901
  /**
   * {@inheritdoc}
   */
902
  public function doSubmitForm(&$form, FormStateInterface &$form_state) {
903
    throw new \LogicException('Use FormBuilderInterface::processForm() instead.');
904 905 906 907 908
  }

  /**
   * {@inheritdoc}
   */
909
  public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) {
910 911 912 913
    // Initialize as unprocessed.
    $element['#processed'] = FALSE;

    // Use element defaults.
914
    if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element['#type']))) {
915 916 917 918 919
      // Overlay $info onto $element, retaining preexisting keys in $element.
      $element += $info;
      $element['#defaults_loaded'] = TRUE;
    }
    // Assign basic defaults common for all form elements.
920
    $element += [
921
      '#required' => FALSE,
922
      '#attributes' => [],
923
      '#title_display' => 'before',
924
      '#description_display' => 'after',
925
      '#errors' => NULL,
926
    ];
927 928 929

    // Special handling if we're on the top level form element.
    if (isset($element['#type']) && $element['#type'] == 'form') {
930
      if (!empty($element['#https']) && !UrlHelper::isExternal($element['#action'])) {
931 932 933 934 935 936 937 938 939
        global $base_root;

        // Not an external URL so ensure that it is secure.
        $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action'];
      }

      // Store a reference to the complete form in $form_state prior to building
      // the form. This allows advanced #process and #after_build callbacks to
      // perform changes elsewhere in the form.
940
      $form_state->setCompleteForm($element);
941 942 943 944

      // Set a flag if we have a correct form submission. This is always TRUE
      // for programmed forms coming from self::submitForm(), or if the form_id
      // coming from the POST data is set and matches the current form_id.
945
      $input = $form_state->getUserInput();
946 947
      if ($form_state->isProgrammed() || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
        $form_state->setProcessInput();
948 949 950 951 952 953 954 955 956 957 958 959 960 961
        if (isset($element['#token'])) {
          $input = $form_state->getUserInput();
          if (empty($input['form_token']) || !$this->csrfToken->validate($input['form_token'], $element['#token'])) {
            // Set an early form error to block certain input processing since
            // that opens the door for CSRF vulnerabilities.
            $this->setInvalidTokenError($form_state);

            // This value is checked in self::handleInputElement().
            $form_state->setInvalidToken(TRUE);

            // Make sure file uploads do not get processed.
            $this->requestStack->getCurrentRequest()->files = new FileBag();
          }
        }
962 963
      }
      else {
964
        $form_state->setProcessInput(FALSE);
965 966 967
      }

      // All form elements should have an #array_parents property.
968
      $element['#array_parents'] = [];
969 970 971
    }

    if (!isset($element['#id'])) {
972 973 974 975 976 977 978 979 980 981
      $unprocessed_id = 'edit-' . implode('-', $element['#parents']);
      $element['#id'] = Html::getUniqueId($unprocessed_id);
      // Provide a selector usable by JavaScript. As the ID is unique, its not
      // possible to rely on it in JavaScript.
      $element['#attributes']['data-drupal-selector'] = Html::getId($unprocessed_id);
    }
    else {
      // Provide a selector usable by JavaScript. As the ID is unique, its not
      // possible to rely on it in JavaScript.
      $element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']);
982 983 984 985 986 987 988 989 990 991 992 993 994 995
    }

    // Add the aria-describedby attribute to associate the form control with its
    // description.
    if (!empty($element['#description'])) {
      $element['#attributes']['aria-describedby'] = $element['#id'] . '--description';
    }
    // Handle input elements.
    if (!empty($element['#input'])) {
      $this->handleInputElement($form_id, $element, $form_state);
    }
    // Allow for elements to expand to multiple elements, e.g., radios,
    // checkboxes and files.
    if (isset($element['#process']) && !$element['#processed']) {
996
      foreach ($element['#process'] as $callback) {
997
        $complete_form = &$form_state->getCompleteForm();
998
        $element = call_user_func_array($form_state->prepareCallback($callback), [&$element, &$form_state, &$complete_form]);
999 1000 1001 1002 1003 1004 1005 1006 1007
      }
      $element['#processed'] = TRUE;
    }

    // We start off assuming all form elements are in the correct order.
    $element['#sorted'] = TRUE;

    // Recurse through all child elements.
    $count = 0;
1008 1009 1010 1011 1012 1013 1014
    if (isset($element['#access'])) {
      $access = $element['#access'];
      $inherited_access = NULL;
      if (($access instanceof AccessResultInterface && !$access->isAllowed()) || $access === FALSE) {
        $inherited_access = $access;
      }
    }
1015
    foreach (Element::children($element) as $key) {
1016 1017
      // Prior to checking properties of child elements, their default
      // properties need to be loaded.
1018
      if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element[$key]['#type']))) {
1019 1020 1021 1022 1023 1024 1025 1026 1027
        $element[$key] += $info;
        $element[$key]['#defaults_loaded'] = TRUE;
      }

      // Don't squash an existing tree value.
      if (!isset($element[$key]['#tree'])) {
        $element[$key]['#tree'] = $element['#tree'];
      }

1028 1029 1030
      // Children inherit #access from parent.
      if (isset($inherited_access)) {
        $element[$key]['#access'] = $inherited_access;
1031 1032 1033 1034
      }

      // Make child elements inherit their parent's #disabled and #allow_focus
      // values unless they specify their own.
1035
      foreach (['#disabled', '#allow_focus'] as $property) {
1036 1037 1038 1039 1040 1041 1042 1043 1044
        if (isset($element[$property]) && !isset($element[$key][$property])) {
          $element[$key][$property] = $element[$property];
        }
      }

      // Don't squash existing parents value.
      if (!isset($element[$key]['#parents'])) {
        // Check to see if a tree of child elements is present. If so,
        // continue down the tree if required.
1045
        $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], [$key]) : [$key];
1046 1047 1048 1049 1050 1051 1052 1053
      }
      // Ensure #array_parents follows the actual form structure.
      $array_parents = $element['#array_parents'];
      $array_parents[] = $key;
      $element[$key]['#array_parents'] = $array_parents;

      // Assign a decimal placeholder weight to preserve original array order.
      if (!isset($element[$key]['#weight'])) {
1054
        $element[$key]['#weight'] = $count / 1000;
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
      }
      else {
        // If one of the child elements has a weight then we will need to sort
        // later.
        unset($element['#sorted']);
      }
      $element[$key] = $this->doBuildForm($form_id, $element[$key], $form_state);
      $count++;
    }

    // The #after_build flag allows any piece of a form to be altered
    // after normal input parsing has been completed.
    if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
1068
      foreach ($element['#after_build'] as $callback) {
1069
        $element = call_user_func_array($form_state->prepareCallback($callback), [$element, &$form_state]);
1070 1071 1072 1073 1074 1075 1076
      }
      $element['#after_build_done'] = TRUE;
    }

    // If there is a file element, we need to flip a flag so later the
    // form encoding can be set.
    if (isset($element['#type']) && $element['#type'] == 'file') {
1077
      $form_state->setHasFileElement();
1078 1079 1080 1081 1082 1083
    }

    // Final tasks for the form element after self::doBuildForm() has run for
    // all other elements.
    if (isset($element['#type']) && $element['#type'] == 'form') {
      // If there is a file element, we set the form encoding.
1084
      if ($form_state->hasFileElement()) {
1085 1086 1087
        $element['#attributes']['enctype'] = 'multipart/form-data';
      }

1088 1089 1090 1091 1092
      // Allow Ajax submissions to the form action to bypass verification. This
      // is especially useful for multipart forms, which cannot be verified via
      // a response header.
      $element['#attached']['drupalSettings']['ajaxTrustedUrl'][$element['#action']] = TRUE;

1093 1094 1095 1096 1097 1098
      // If a form contains a single textfield, and the ENTER key is pressed
      // within it, Internet Explorer submits the form with no POST data
      // identifying any submit button. Other browsers submit POST data as
      // though the user clicked the first button. Therefore, to be as
      // consistent as we can be across browsers, if no 'triggering_element' has
      // been identified yet, default it to the first button.
1099 1100 1101
      $buttons = $form_state->getButtons();
      if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) {
        $form_state->setTriggeringElement($buttons[0]);
1102 1103
      }

1104
      $triggering_element = $form_state->getTriggeringElement();
1105 1106 1107
      // If the triggering element specifies "button-level" validation and
      // submit handlers to run instead of the default form-level ones, then add
      // those to the form state.
1108 1109 1110 1111 1112
      if (isset($triggering_element['#validate'])) {
        $form_state->setValidateHandlers($triggering_element['#validate']);
      }
      if (isset($triggering_element['#submit'])) {
        $form_state->setSubmitHandlers($triggering_element['#submit']);
1113 1114 1115 1116
      }

      // If the triggering element executes submit handlers, then set the form
      // state key that's needed for those handlers to run.
1117
      if (!empty($triggering_element['#executes_submit_callback'])) {
1118
        $form_state->setSubmitted();
1119 1120 1121
      }

      // Special processing if the triggering element is a button.
1122
      if (!empty($triggering_element['#is_button'])) {
1123 1124 1125 1126 1127 1128
        // Because there are several ways in which the triggering element could
        // have been determined (including from input variables set by
        // JavaScript or fallback behavior implemented for IE), and because
        // buttons often have their #name property not derived from their
        // #parents property, we can't assume that input processing that's
        // happened up until here has resulted in
1129
        // $form_state->getValue(BUTTON_NAME) being set. But it's common for
1130
        // forms to have several buttons named 'op' and switch on
1131 1132
        // $form_state->getValue('op') during submit handler execution.
        $form_state->setValue($triggering_element['#name'], $triggering_element['#value']);
1133 1134 1135 1136 1137
      }
    }
    return $element;
  }

1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
  /**
   * Helper function to normalize the different callable formats.
   *
   * @param callable $value_callable
   *   The callable to be checked.
   *
   * @return bool
   *   TRUE if the callable is safe even if the CSRF token is invalid, FALSE
   *   otherwise.
   */
  protected function valueCallableIsSafe(callable $value_callable) {
    // The same static class method callable may be formatted in two array and
    // two string forms:
    // ['\Classname', 'methodname']
    // ['Classname', 'methodname']
    // '\Classname::methodname'
    // 'Classname::methodname'
    if (is_callable($value_callable, FALSE, $callable_name)) {
      // The third parameter of is_callable() is set to a string form, but we
      // still have to normalize further by stripping a leading '\'.
      return in_array(ltrim($callable_name, '\\'), $this->safeCoreValueCallables);
    }
    return FALSE;
  }

1163 1164 1165
  /**
   * Adds the #name and #value properties of an input element before rendering.
   */
1166
  protected function handleInputElement($form_id, &$element, FormStateInterface &$form_state) {
1167 1168 1169 1170
    if (!isset($element['#name'])) {
      $name = array_shift($element['#parents']);
      $element['#name'] = $name;
      if ($element['#type'] == 'file') {
1171
        // To make it easier to handle files in file.inc, we place all
1172 1173
        // file fields in the 'files' array. Also, we do not support
        // nested file names.
1174
        // @todo Remove this files prefix now?
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
        $element['#name'] = 'files[' . $element['#name'] . ']';
      }
      elseif (count($element['#parents'])) {
        $element['#name'] .= '[' . implode('][', $element['#parents']) . ']';
      }
      array_unshift($element['#parents'], $name);
    }

    // Setting #disabled to TRUE results in user input being ignored regardless
    // of how the element is themed or whether JavaScript is used to change the
    // control's attributes. However, it's good UI to let the user know that
    // input is not wanted for the control. HTML supports two attributes for:
    // this: http://www.w3.org/TR/html401/interact/forms.html#h-17.12. If a form
    // wants to start a control off with one of these attributes for UI
    // purposes, only, but still allow input to be processed if it's submitted,
    // it can set the desired attribute in #attributes directly rather than
    // using #disabled. However, developers should think carefully about the
    // accessibility implications of doing so: if the form expects input to be
    // enterable under some condition triggered by JavaScript, how would someone
    // who has JavaScript disabled trigger that condition? Instead, developers
    // should consider whether a multi-step form would be more appropriate
    // (#disabled can be changed from step to step). If one still decides to use
    // JavaScript to affect when a control is enabled, then it is best for
    // accessibility for the control to be enabled in the HTML, and disabled by
    // JavaScript on document ready.
    if (!empty($element['#disabled'])) {
      if (!empty($element['#allow_focus'])) {
        $element['#attributes']['readonly'] = 'readonly';
      }
      else {
        $element['#attributes']['disabled'] = 'disabled';
      }
    }

    // With JavaScript or other easy hacking, input can be submitted even for
    // elements with #access=FALSE or #disabled=TRUE. For security, these must
    // not be processed. Forms that set #disabled=TRUE on an element do not
    // expect input for the element, and even forms submitted with
    // self::submitForm() must not be able to get around this. Forms that set
    // #access=FALSE on an element usually allow access for some users, so forms
    // submitted with self::submitForm() may bypass access restriction and be
    // treated as high-privilege users instead.
1217
    $process_input = empty($element['#disabled']) && (($form_state->isProgrammed() && $form_state->isBypassingProgrammedAccessChecks()) || ($form_state->isProcessingInput() && (!isset($element['#access']) || $element['#access'])));
1218 1219 1220

    // Set the element's #value property.
    if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
1221 1222 1223
      // @todo Once all elements are converted to plugins in
      //   https://www.drupal.org/node/2311393, rely on
      //   $element['#value_callback'] directly.
1224
      $value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
1225 1226 1227 1228
      if (!is_callable($value_callable)) {
        $value_callable = '\Drupal\Core\Render\Element\FormElement::valueCallback';
      }

1229 1230 1231 1232
      if ($process_input) {
        // Get the input for the current element. NULL values in the input need
        // to be explicitly distinguished from missing input. (see below)
        $input_exists = NULL;
webchick's avatar