FormBuilder.php 48.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
<?php

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

namespace Drupal\Core\Form;

use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\NestedArray;
12
use Drupal\Component\Utility\SafeMarkup;
13
use Drupal\Component\Utility\String;
14
use Drupal\Component\Utility\UrlHelper;
15
use Drupal\Core\Access\CsrfTokenGenerator;
16
use Drupal\Core\DependencyInjection\ClassResolverInterface;
17
use Drupal\Core\Extension\ModuleHandlerInterface;
18
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
19
use Drupal\Core\Render\Element;
20
use Drupal\Core\Site\Settings;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\HttpFoundation\RequestStack;
23
24
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
25
use Symfony\Component\HttpKernel\HttpKernel;
26
27
28
29
30
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;

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

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

  /**
   * The factory for expirable key value stores used by form cache.
   *
46
   * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
47
48
49
50
51
52
53
54
55
56
57
   */
  protected $keyValueExpirableFactory;

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

  /**
58
   * The request stack.
59
   *
60
   * @var \Symfony\Component\HttpFoundation\RequestStack
61
   */
62
  protected $requestStack;
63
64
65
66
67
68
69
70
71
72
73

  /**
   * The CSRF token generator to validate the form token.
   *
   * @var \Drupal\Core\Access\CsrfTokenGenerator
   */
  protected $csrfToken;

  /**
   * The HTTP kernel to handle forms returning response objects.
   *
74
   * @var \Symfony\Component\HttpKernel\HttpKernel
75
76
77
   */
  protected $httpKernel;

78
79
80
81
82
83
84
  /**
   * The class resolver.
   *
   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
   */
  protected $classResolver;

85
86
87
88
89
90
91
92
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
93
   * @var \Drupal\Core\Form\FormValidatorInterface
94
   */
95
  protected $formValidator;
96

97
98
99
100
101
  /**
   * @var \Drupal\Core\Form\FormSubmitterInterface
   */
  protected $formSubmitter;

102
103
104
  /**
   * Constructs a new FormBuilder.
   *
105
106
   * @param \Drupal\Core\Form\FormValidatorInterface $form_validator
   *   The form validator.
107
108
   * @param \Drupal\Core\Form\FormSubmitterInterface $form_submitter
   *   The form submission processor.
109
110
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
111
   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
112
113
114
   *   The keyvalue expirable factory.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
115
116
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
117
118
   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
   *   The class resolver.
119
120
121
122
123
   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
   *   The CSRF token generator.
   * @param \Drupal\Core\HttpKernel $http_kernel
   *   The HTTP kernel.
   */
124
  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
125
    $this->formValidator = $form_validator;
126
    $this->formSubmitter = $form_submitter;
127
128
129
    $this->moduleHandler = $module_handler;
    $this->keyValueExpirableFactory = $key_value_expirable_factory;
    $this->eventDispatcher = $event_dispatcher;
130
    $this->requestStack = $request_stack;
131
    $this->classResolver = $class_resolver;
132
133
134
135
136
137
138
    $this->csrfToken = $csrf_token;
    $this->httpKernel = $http_kernel;
  }

  /**
   * {@inheritdoc}
   */
139
  public function getFormId($form_arg, FormStateInterface &$form_state) {
140
141
    // If the $form_arg is the name of a class, instantiate it. Don't allow
    // arbitrary strings to be passed to the class resolver.
142
    if (is_string($form_arg) && class_exists($form_arg)) {
143
      $form_arg = $this->classResolver->getInstanceFromDefinition($form_arg);
144
    }
145

146
147
    if (!is_object($form_arg) || !($form_arg instanceof FormInterface)) {
      throw new \InvalidArgumentException(String::format('The form argument @form_arg is not a valid form.', array('@form_arg' => $form_arg)));
148
149
    }

150
    // Add the $form_arg as the callback object and determine the form ID.
151
    $form_state->addBuildInfo('callback_object', $form_arg);
152
    if ($form_arg instanceof BaseFormIdInterface) {
153
      $form_state->addBuildInfo('base_form_id', $form_arg->getBaseFormID());
154
155
    }
    return $form_arg->getFormId();
156
157
158
159
160
161
  }

  /**
   * {@inheritdoc}
   */
  public function getForm($form_arg) {
162
    $form_state = new FormState();
163
164
165

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

169
    return $this->buildForm($form_arg, $form_state);
170
171
172
173
174
  }

  /**
   * {@inheritdoc}
   */
175
  public function buildForm($form_id, FormStateInterface &$form_state) {
176
177
178
    // Ensure the form ID is prepared.
    $form_id = $this->getFormId($form_id, $form_state);

179
    if (!isset($form_state['input'])) {
180
      $request = $this->requestStack->getCurrentRequest();
181
      $form_state->set('input', $form_state['method'] == 'get' ? $request->query->all() : $request->request->all());
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    }

    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.
    $check_cache = isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id']);
    if ($check_cache) {
      $form = $this->getCache($form_state['input']['form_build_id'], $form_state);
    }

    // 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) {
210
        $form_state_before_retrieval = clone $form_state;
211
212
213
214
215
      }

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

216
217
      // self::setCache() removes uncacheable $form_state keys (see properties
      // in \Drupal\Core\Form\FormState) in order for multi-step forms to work
218
219
      // properly. This means that form processing logic for single-step forms
      // using $form_state['cache'] may depend on data stored in those keys
220
221
222
      // 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
223
224
225
226
227
228
229
230
      // 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) {
231
232
233
        $cache_form_state = $form_state->getCacheableArray(array('always_process', 'temporary'));
        $form_state = $form_state_before_retrieval;
        $form_state->setFormState($cache_form_state);
234
235
236
237
238
239
      }
    }

    // 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
240
    //   corresponding elements and into $form_state->getValues().
241
242
243
244
245
246
247
248
249
250
251
252
    // - 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);

    // If the form returns some kind of response, deliver it.
    if ($response instanceof Response) {
      $this->sendResponse($response);
253
      exit;
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
    }

    // 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
    // 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}
   */
270
  public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) {
271
272
273
274
275
276
277
278
279
280
281
282
283
    $form = $this->retrieveForm($form_id, $form_state);

    // If only parts of the form will be returned to the browser (e.g., Ajax or
    // RIA clients), re-use the old #build_id to not require client-side code to
    // manually update the hidden 'build_id' input element.
    // 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()
    if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) {
      $form['#build_id'] = $old_form['#build_id'];
    }
    else {
284
      $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    }

    // #action defaults to request_uri(), 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.
    if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) {
      $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.
    if (empty($form_state['no_cache'])) {
      $this->setCache($form['#build_id'], $form, $form_state);
    }

    // Clear out all group associations as these might be different when
    // re-rendering the form.
306
    $form_state->set('groups', array());
307
308
309
310
311
312
313
314

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

  /**
   * {@inheritdoc}
   */
315
  public function getCache($form_build_id, FormStateInterface &$form_state) {
316
317
318
319
320
    if ($form = $this->keyValueExpirableFactory->get('form')->get($form_build_id)) {
      $user = $this->currentUser();
      if ((isset($form['#cache_token']) && $this->csrfToken->validate($form['#cache_token'])) || (!isset($form['#cache_token']) && $user->isAnonymous())) {
        if ($stored_form_state = $this->keyValueExpirableFactory->get('form_state')->get($form_build_id)) {
          // Re-populate $form_state for subsequent rebuilds.
321
          $form_state->setFormState($stored_form_state);
322
323
324
325
326
327
328
329
330
331
332
333
334

          // If the original form is contained in include files, load the files.
          // @see form_load_include()
          $form_state['build_info'] += array('files' => array());
          foreach ($form_state['build_info']['files'] as $file) {
            if (is_array($file)) {
              $file += array('type' => 'inc', 'name' => $file['module']);
              $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']);
            }
            elseif (file_exists($file)) {
              require_once DRUPAL_ROOT . '/' . $file;
            }
          }
335
336
337
338
339
340
341
          // Retrieve the list of previously known safe strings and store it
          // for this request.
          // @todo Ensure we are not storing an excessively large string list
          //   in: https://www.drupal.org/node/2295823
          $form_state['build_info'] += array('safe_strings' => array());
          SafeMarkup::setMultiple($form_state['build_info']['safe_strings']);
          unset($form_state['build_info']['safe_strings']);
342
343
344
345
346
347
348
349
350
        }
        return $form;
      }
    }
  }

  /**
   * {@inheritdoc}
   */
351
  public function setCache($form_build_id, $form, FormStateInterface $form_state) {
352
353
354
355
356
357
358
359
360
361
362
363
    // 6 hours cache life time for forms should be plenty.
    $expire = 21600;

    // Cache form structure.
    if (isset($form)) {
      if ($this->currentUser()->isAuthenticated()) {
        $form['#cache_token'] = $this->csrfToken->get();
      }
      $this->keyValueExpirableFactory->get('form')->setWithExpire($form_build_id, $form, $expire);
    }

    // Cache form state.
364
365
366
    // Store the known list of safe strings for form re-use.
    // @todo Ensure we are not storing an excessively large string list in:
    //   https://www.drupal.org/node/2295823
367
    $form_state->addBuildInfo('safe_strings', SafeMarkup::getAll());
368

369
    if ($data = $form_state->getCacheableArray()) {
370
371
372
373
374
375
376
      $this->keyValueExpirableFactory->get('form_state')->setWithExpire($form_build_id, $data, $expire);
    }
  }

  /**
   * {@inheritdoc}
   */
377
  public function submitForm($form_arg, FormStateInterface &$form_state) {
378
379
    if (!isset($form_state['build_info']['args'])) {
      $args = func_get_args();
380
381
      // Remove $form and $form_state from the arguments.
      unset($args[0], $args[1]);
382
      $form_state->addBuildInfo('args', array_values($args));
383
384
385
386
387
388
    }

    // Populate $form_state['input'] with the submitted values before retrieving
    // the form, to be consistent with what self::buildForm() does for
    // non-programmatic submissions (form builder functions may expect it to be
    // there).
389
    $form_state->set('input', $form_state->getValues());
390

391
    $form_state->set('programmed', TRUE);
392
393
394
395

    $form_id = $this->getFormId($form_arg, $form_state);
    $form = $this->retrieveForm($form_id, $form_state);
    // Programmed forms are always submitted.
396
    $form_state->set('submitted', TRUE);
397
398

    // Reset form validation.
399
    $form_state->set('must_validate', TRUE);
400
    $form_state->clearErrors();
401
402
403
404
405
406
407
408

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

  /**
   * {@inheritdoc}
   */
409
  public function retrieveForm($form_id, FormStateInterface &$form_state) {
410
    // Record the $form_id.
411
    $form_state->addBuildInfo('form_id', $form_id);
412
413
414
415
416
417

    // 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.
    $args = $form_state['build_info']['args'];

418
    $callback = array($form_state['build_info']['callback_object'], 'buildForm');
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438

    $form = array();
    // 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.
    $form['#attributes']['class'][] = $this->drupalHtmlClass($form_id);
    // Same for the base form ID, if any.
    if (isset($form_state['build_info']['base_form_id'])) {
      $form['#attributes']['class'][] = $this->drupalHtmlClass($form_state['build_info']['base_form_id']);
    }

    // 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.
    $args = array_merge(array($form, &$form_state), $args);

    $form = call_user_func_array($callback, $args);
    // If the form returns some kind of response, deliver it.
    if ($form instanceof Response) {
      $this->sendResponse($form);
439
      exit;
440
441
442
443
444
445
446
447
448
    }
    $form['#form_id'] = $form_id;

    return $form;
  }

  /**
   * {@inheritdoc}
   */
449
450
  public function processForm($form_id, &$form, FormStateInterface &$form_state) {
    $form_state->set('values', array());
451

452
    // With GET, these forms are always submitted if requested.
453
    if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) {
454
455
456
      $input = $form_state->get('input');
      if (!isset($input['form_build_id'])) {
        $input['form_build_id'] = $form['#build_id'];
457
      }
458
459
      if (!isset($input['form_id'])) {
        $input['form_id'] = $form_id;
460
      }
461
462
      if (!isset($input['form_token']) && isset($form['#token'])) {
        $input['form_token'] = $this->csrfToken->get($form['#token']);
463
      }
464
      $form_state->set('input', $input);
465
466
467
468
    }

    // self::doBuildForm() finishes building the form by calling element
    // #process functions and mapping user input, if any, to #value properties,
469
470
    // and also storing the values in $form_state->getValues(). We need to
    // retain the unprocessed $form in case it needs to be cached.
471
472
473
474
475
    $unprocessed_form = $form;
    $form = $this->doBuildForm($form_id, $form, $form_state);

    // Only process the input if we have a correct form submission.
    if ($form_state['process_input']) {
476
477
478
479
480
      // Form constructors may explicitly set #token to FALSE when cross site
      // request forgery is irrelevant to the form, such as search forms.
      if (isset($form['#token']) && $form['#token'] === FALSE) {
        unset($form['#token']);
      }
481
482
483
484
485
486
487
      // 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.
      if ($form_state['programmed'] && !isset($form_state['triggering_element']) && count($form_state['buttons']) == 1) {
488
        $form_state->set('triggering_element', reset($form_state['buttons']));
489
      }
490
      $this->formValidator->validateForm($form_id, $form, $form_state);
491
492
493
494
495
496

      // drupal_html_id() 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.
497
      if (!FormState::hasAnyErrors()) {
498
499
500
501
        // In case of errors, do not break HTML IDs of other forms.
        $this->drupalStaticReset('drupal_html_id');
      }

502
      if (!$form_state['rebuild'] && !FormState::hasAnyErrors()) {
503
504
        if ($submit_response = $this->formSubmitter->doSubmitForm($form, $form_state)) {
          return $submit_response;
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
        }
      }

      // Don't rebuild or cache form submissions invoked via self::submitForm().
      if (!empty($form_state['programmed'])) {
        return;
      }

      // If $form_state['rebuild'] 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['rebuild'] 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['rebuild']. 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['rebuild'].
      // @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.
528
      if (($form_state['rebuild'] || !$form_state['executed']) && !FormState::hasAnyErrors()) {
529
530
531
        // Form building functions (e.g., self::handleInputElement()) may use
        // $form_state['rebuild'] to determine if they are running in the
        // context of a rebuild, so ensure it is set.
532
        $form_state->setRebuild();
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
        $form = $this->rebuildForm($form_id, $form_state, $form);
      }
    }

    // After processing the form, the form builder or a #process callback may
    // have set $form_state['cache'] to indicate that the form and form state
    // shall be cached. But the form may only be cached if the 'no_cache'
    // property is not set to TRUE. 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['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) {
      $this->setCache($form['#build_id'], $unprocessed_form, $form_state);
    }
  }

  /**
   * {@inheritdoc}
   */
552
  public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
553
554
555
    $user = $this->currentUser();

    $form['#type'] = 'form';
556
    $form_state->set('programmed', isset($form_state['programmed']) ? $form_state['programmed'] : FALSE);
557
558

    // Fix the form method, if it is 'get' in $form_state, but not in $form.
559
    if ($form_state->get('method') == 'get' && !isset($form['#method'])) {
560
561
562
563
564
565
566
567
568
569
      $form['#method'] = 'get';
    }

    // 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'])) {
570
      $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
    }
    $form['form_build_id'] = array(
      '#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.
      '#parents' => array('form_build_id'),
    );

    // 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.
    if ($user && $user->isAuthenticated() && !$form_state['programmed']) {
      // Form constructors may explicitly set #token to FALSE when cross site
      // request forgery is irrelevant to the form, such as search forms.
      if (isset($form['#token']) && $form['#token'] === FALSE) {
        unset($form['#token']);
      }
      // Otherwise, generate a public token based on the form id.
      else {
        $form['#token'] = $form_id;
        $form['form_token'] = array(
          '#id' => $this->drupalHtmlId('edit-' . $form_id . '-form-token'),
          '#type' => 'token',
          '#default_value' => $this->csrfToken->get($form['#token']),
          // Form processing and validation requires this value, so ensure the
          // submitted form value appears literally, regardless of custom #tree
          // and #parents being set elsewhere.
          '#parents' => array('form_token'),
        );
      }
    }

    if (isset($form_id)) {
      $form['form_id'] = array(
        '#type' => 'hidden',
        '#value' => $form_id,
        '#id' => $this->drupalHtmlId("edit-$form_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.
        '#parents' => array('form_id'),
      );
    }
    if (!isset($form['#id'])) {
      $form['#id'] = $this->drupalHtmlId($form_id);
    }

    $form += $this->getElementInfo('form');
    $form += array('#tree' => FALSE, '#parents' => array());
629
630
    $form['#validate'][] = array($form_state['build_info']['callback_object'], 'validateForm');
    $form['#submit'][] = array($form_state['build_info']['callback_object'], 'submitForm');
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655

    // If no #theme has been set, automatically apply theme suggestions.
    // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the
    // #theme function only has to care for rendering the inner form elements,
    // not the form itself.
    if (!isset($form['#theme'])) {
      $form['#theme'] = array($form_id);
      if (isset($form_state['build_info']['base_form_id'])) {
        $form['#theme'][] = $form_state['build_info']['base_form_id'];
      }
    }

    // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and
    // hook_form_FORM_ID_alter() implementations.
    $hooks = array('form');
    if (isset($form_state['build_info']['base_form_id'])) {
      $hooks[] = 'form_' . $form_state['build_info']['base_form_id'];
    }
    $hooks[] = 'form_' . $form_id;
    $this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
  }

  /**
   * {@inheritdoc}
   */
656
  public function validateForm($form_id, &$form, FormStateInterface &$form_state) {
657
    $this->formValidator->validateForm($form_id, $form, $form_state);
658
659
  }

660
661
662
  /**
   * {@inheritdoc}
   */
663
  public function redirectForm(FormStateInterface $form_state) {
664
    return $this->formSubmitter->redirectForm($form_state);
665
666
667
  }

  /**
668
   * {@inheritdoc}
669
   */
670
  public function executeValidateHandlers(&$form, FormStateInterface &$form_state) {
671
    $this->formValidator->executeValidateHandlers($form, $form_state);
672
673
674
675
676
  }

  /**
   * {@inheritdoc}
   */
677
  public function executeSubmitHandlers(&$form, FormStateInterface &$form_state) {
678
679
    $this->formSubmitter->executeSubmitHandlers($form, $form_state);
  }
680

681
682
683
  /**
   * {@inheritdoc}
   */
684
  public function doSubmitForm(&$form, FormStateInterface &$form_state) {
685
    throw new \LogicException('Use FormBuilderInterface::processForm() instead.');
686
687
688
689
690
  }

  /**
   * {@inheritdoc}
   */
691
  public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) {
692
693
694
695
696
697
698
699
700
701
702
703
704
705
    // Initialize as unprocessed.
    $element['#processed'] = FALSE;

    // Use element defaults.
    if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->getElementInfo($element['#type']))) {
      // Overlay $info onto $element, retaining preexisting keys in $element.
      $element += $info;
      $element['#defaults_loaded'] = TRUE;
    }
    // Assign basic defaults common for all form elements.
    $element += array(
      '#required' => FALSE,
      '#attributes' => array(),
      '#title_display' => 'before',
706
      '#description_display' => 'after',
707
      '#errors' => NULL,
708
709
710
711
    );

    // Special handling if we're on the top level form element.
    if (isset($element['#type']) && $element['#type'] == 'form') {
712
      if (!empty($element['#https']) && Settings::get('mixed_mode_sessions', FALSE) &&
713
        !UrlHelper::isExternal($element['#action'])) {
714
715
716
717
718
719
720
721
722
        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.
723
      $form_state->setCompleteForm($element);
724
725
726
727
728

      // 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.
      if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) {
729
        $form_state->set('process_input', TRUE);
730
731
      }
      else {
732
        $form_state->set('process_input', FALSE);
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
      }

      // All form elements should have an #array_parents property.
      $element['#array_parents'] = array();
    }

    if (!isset($element['#id'])) {
      $element['#id'] = $this->drupalHtmlId('edit-' . implode('-', $element['#parents']));
    }

    // 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']) {
      foreach ($element['#process'] as $process) {
756
757
        $complete_form = &$form_state->getCompleteForm();
        $element = call_user_func_array($process, array(&$element, &$form_state, &$complete_form));
758
759
760
761
762
763
764
765
766
      }
      $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;
767
    foreach (Element::children($element) as $key) {
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
      // Prior to checking properties of child elements, their default
      // properties need to be loaded.
      if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->getElementInfo($element[$key]['#type']))) {
        $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'];
      }

      // Deny access to child elements if parent is denied.
      if (isset($element['#access']) && !$element['#access']) {
        $element[$key]['#access'] = FALSE;
      }

      // Make child elements inherit their parent's #disabled and #allow_focus
      // values unless they specify their own.
      foreach (array('#disabled', '#allow_focus') as $property) {
        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.
        $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key);
      }
      // 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'])) {
        $element[$key]['#weight'] = $count/1000;
      }
      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'])) {
      foreach ($element['#after_build'] as $callable) {
        $element = call_user_func_array($callable, array($element, &$form_state));
      }
      $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') {
829
      $form_state->set('has_file_element', TRUE);
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
    }

    // 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.
      if (isset($form_state['has_file_element'])) {
        $element['#attributes']['enctype'] = 'multipart/form-data';
      }

      // 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.
      if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) {
847
        $form_state->set('triggering_element', $form_state['buttons'][0]);
848
849
      }

850
      $triggering_element = $form_state->get('triggering_element');
851
852
853
854
      // 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.
      foreach (array('validate', 'submit') as $type) {
855
856
        if (isset($triggering_element['#' . $type])) {
          $form_state->set($type . '_handlers', $triggering_element['#' . $type]);
857
858
859
860
861
        }
      }

      // If the triggering element executes submit handlers, then set the form
      // state key that's needed for those handlers to run.
862
863
      if (!empty($triggering_element['#executes_submit_callback'])) {
        $form_state->set('submitted', TRUE);
864
865
866
      }

      // Special processing if the triggering element is a button.
867
      if (!empty($triggering_element['#is_button'])) {
868
869
870
871
872
873
        // 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
874
        // $form_state->getValue(BUTTON_NAME) being set. But it's common for
875
        // forms to have several buttons named 'op' and switch on
876
877
        // $form_state->getValue('op') during submit handler execution.
        $form_state->setValue($triggering_element['#name'], $triggering_element['#value']);
878
879
880
881
882
883
884
885
      }
    }
    return $element;
  }

  /**
   * Adds the #name and #value properties of an input element before rendering.
   */
886
  protected function handleInputElement($form_id, &$element, FormStateInterface &$form_state) {
887
888
889
890
    if (!isset($element['#name'])) {
      $name = array_shift($element['#parents']);
      $element['#name'] = $name;
      if ($element['#type'] == 'file') {
891
        // To make it easier to handle files in file.inc, we place all
892
893
        // file fields in the 'files' array. Also, we do not support
        // nested file names.
894
        // @todo Remove this files prefix now?
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
        $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.
937
    $process_input = empty($element['#disabled']) && (($form_state['programmed'] && $form_state['programmed_bypass_access_check']) || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
938
939
940

    // Set the element's #value property.
    if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
941
      $value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
      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;
        $input = NestedArray::getValue($form_state['input'], $element['#parents'], $input_exists);
        // For browser-submitted forms, the submitted values do not contain
        // values for certain elements (empty multiple select, unchecked
        // checkbox). During initial form processing, we add explicit NULL
        // values for such elements in $form_state['input']. When rebuilding the
        // form, we can distinguish elements having NULL input from elements
        // that were not part of the initially submitted form and can therefore
        // use default values for the latter, if required. Programmatically
        // submitted forms can submit explicit NULL values when calling
        // self::submitForm() so we do not modify $form_state['input'] for them.
        if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) {
          // Add the necessary parent keys to $form_state['input'] and sets the
          // element's input value to NULL.
          NestedArray::setValue($form_state['input'], $element['#parents'], NULL);
          $input_exists = TRUE;
        }
        // If we have input for the current element, assign it to the #value
        // property, optionally filtered through $value_callback.
        if ($input_exists) {
965
966
          if (is_callable($value_callable)) {
            $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
967
968
969
970
971
972
973
974
975
976
977
978
979
980
          }
          if (!isset($element['#value']) && isset($input)) {
            $element['#value'] = $input;
          }
        }
        // Mark all posted values for validation.
        if (isset($element['#value']) || (!empty($element['#required']))) {
          $element['#needs_validation'] = TRUE;
        }
      }
      // Load defaults.
      if (!isset($element['#value'])) {
        // Call #type_value without a second argument to request default_value
        // handling.
981
982
        if (is_callable($value_callable)) {
          $element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
        }
        // Final catch. If we haven't set a value yet, use the explicit default
        // value. Avoid image buttons (which come with garbage value), so we
        // only get value for the button actually clicked.
        if (!isset($element['#value']) && empty($element['#has_garbage_value'])) {
          $element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : '';
        }
      }
    }

    // Determine which element (if any) triggered the submission of the form and
    // keep track of all the clickable buttons in the form for
    // form_state_values_clean(). Enforce the same input processing restrictions
    // as above.
    if ($process_input) {
      // Detect if the element triggered the submission via Ajax.
      if ($this->elementTriggeredScriptedSubmission($element, $form_state)) {
1000
        $form_state->set('triggering_element', $element);
1001
1002
1003
1004
1005
1006
1007
1008
1009
      }

      // If the form was submitted by the browser rather than via Ajax, then it
      // can only have been triggered by a button, and we need to determine
      // which button within the constraints of how browsers provide this
      // information.
      if (!empty($element['#is_button'])) {
        // All buttons in the form need to be tracked for
        // form_state_values_clean() and for the self::doBuildForm() code that
1010
1011
        // handles a form submission containing no button information in
        // \Drupal::request()->request.
1012
1013
1014
        $buttons = $form_state->get('buttons');
        $buttons[] = $element;
        $form_state->set('buttons', $buttons);
1015
        if ($this->buttonWasClicked($element, $form_state)) {
1016
          $form_state->set('triggering_element', $element);
1017
1018
1019
1020
        }
      }
    }

1021
    // Set the element's value in $form_state->getValues(), but only, if its key
1022
    // does not exist yet (a #value_callback may have already populated it).
1023
    if (!NestedArray::keyExists($form_state->getValues(), $element['#parents'])) {
1024
      $form_state->setValueForElement($element, $element['#value']);
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
    }
  }

  /**
   * Detects if an element triggered the form submission via Ajax.
   *
   * This detects button or non-button controls that trigger a form submission
   * via Ajax or some other scriptable environment. These environments can set
   * the special input key '_triggering_element_name' to identify the triggering
   * element. If the name alone doesn't identify the element uniquely, the input
   * key '_triggering_element_value' may also be set to require a match on
   * element value. An example where this is needed is if there are several
   * // buttons all named 'op', and only differing in their value.
   */
1039
  protected function elementTriggeredScriptedSubmission($element, FormStateInterface &$form_state) {
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
    if (!empty($form_state['input']['_triggering_element_name']) && $element['#name'] == $form_state['input']['_triggering_element_name']) {
      if (empty($form_state['input']['_triggering_element_value']) || $form_state['input']['_triggering_element_value'] == $element['#value']) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Determines if a given button triggered the form submission.
   *
   * This detects button controls that trigger a form submission by being
   * clicked and having the click processed by the browser rather than being
   * captured by JavaScript. Essentially, it detects if the button's name and
   * value are part of the POST data, but with extra code to deal with the
   * convoluted way in which browsers submit data for image button clicks.
   *
   * This does not detect button clicks processed by Ajax (that is done in
   * self::elementTriggeredScriptedSubmission()) and it does not detect form
   * submissions from Internet Explorer in response to an ENTER key pressed in a
   * textfield (self::doBuildForm() has extra code for that).
   *
   * Because this function contains only part of the logic needed to determine
   * $form_state['triggering_element'], it should not be called from anywhere
   * other than within the Form API. Form validation and submit handlers needing
   * to know which button was clicked should get that information from
   * $form_state['triggering_element'].
   */
1068
  protected function buttonWasClicked($element, FormStateInterface &$form_state) {
1069
1070
1071
1072
    // First detect normal 'vanilla' button clicks. Traditionally, all standard
    // buttons on a form share the same name (usually 'op'), and the specific
    // return value is used to determine which was clicked. This ONLY works as
    // long as $form['#name'] puts the value at the top level of the tree of
1073
    // \Drupal::request()->request data.
1074
1075
1076
1077
    if (isset($form_state['input'][$element['#name']]) && $form_state['input'][$element['#name']] == $element['#value']) {
      return TRUE;
    }
    // When image buttons are clicked, browsers do NOT pass the form element
1078
1079
1080
1081
    // value in \Drupal::request()->Request. Instead they pass an integer
    // representing the coordinates of the click on the button image. This means
    // that image buttons MUST have unique $form['#name'] values, but the
    // details of their \Drupal::request()->request data should be ignored.
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
    elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Triggers kernel.response and sends a form response.
   *
   * @param \Symfony\Component\HttpFoundation\Response $response
   *   A response object.
   */
  protected function sendResponse(Response $response) {
1095
1096
    $request = $this->requestStack->getCurrentRequest();
    $event = new FilterResponseEvent($this->httpKernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
1097
1098
1099
1100

    $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event);
    // Prepare and send the response.
    $event->getResponse()
1101
      ->prepare($request)
1102
      ->send();
1103
    $this->httpKernel->terminate($request, $response);
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
  }

  /**
   * Wraps element_info().
   *
   * @return array
   */
  protected function getElementInfo($type) {
    return element_info($type);
  }

  /**
   * Wraps drupal_html_class().
   *
   * @return string
   */
  protected function drupalHtmlClass($class) {
    return drupal_html_class($class);
  }

  /**
   * Wraps drupal_html_id().
   *
   * @return string
   */
  protected function drupalHtmlId($id) {
    return drupal_html_id($id);
  }

  /**
   * Wraps drupal_static_reset().
   */
  protected function drupalStaticReset($name = NULL) {
    drupal_static_reset($name);
  }

  /**
   * Gets the current active user.
   *
   * @return \Drupal\Core\Session\AccountInterface
   */
  protected function currentUser() {
    if (!$this->currentUser) {
1147
      if (\Drupal::hasService('current_user')) {
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
        $this->currentUser = \Drupal::currentUser();
      }
      else {
        global $user;
        $this->currentUser = $user;
      }
    }
    return $this->currentUser;
  }

}