You are here

public function FormBuilder::prepareForm in Drupal 9

Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::prepareForm()
  2. 10 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::prepareForm()

Prepares a structured form array.

Adds required elements, executes any hook_form_alter functions, and optionally inserts a validation token to prevent tampering.

Parameters

string $form_id: A unique string identifying the form for validation, submission, theming, and hook_form_alter functions.

array $form: An associative array containing the structure of the form.

\Drupal\Core\Form\FormStateInterface $form_state: The current state of the form. Passed in here so that hook_form_alter() calls can use it, as well.

Overrides FormBuilderInterface::prepareForm

3 calls to FormBuilder::prepareForm()
FormBuilder::buildForm in core/lib/Drupal/Core/Form/FormBuilder.php
Builds and processes a form for a given form ID.
FormBuilder::rebuildForm in core/lib/Drupal/Core/Form/FormBuilder.php
Constructs a new $form from the information in $form_state.
FormBuilder::submitForm in core/lib/Drupal/Core/Form/FormBuilder.php
Retrieves, populates, and processes a form.

File

core/lib/Drupal/Core/Form/FormBuilder.php, line 682

Class

FormBuilder
Provides form building and processing.

Namespace

Drupal\Core\Form

Code

public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
  $user = $this
    ->currentUser();
  $form['#type'] = 'form';

  // Only update the action if it is not already set.
  if (!isset($form['#action'])) {

    // 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 https://www.drupal.org/node/2562341.
    // The placeholder uses a unique string that is returned by
    // Crypt::hashBase64('Drupal\Core\Form\FormBuilder::prepareForm').
    $placeholder = 'form_action_p_pvdeGsVG5zNF_XLGPTvYSKCf43t8qZYSwcfZl2uzM';
    $form['#attached']['placeholders'][$placeholder] = [
      '#lazy_builder' => [
        'form_builder:renderPlaceholderFormAction',
        [],
      ],
    ];
    $form['#action'] = $placeholder;
  }

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

  // GET forms should not use a CSRF token.
  if (isset($form['#method']) && $form['#method'] === 'get') {
    $form += [
      '#token' => FALSE,
    ];
  }

  // 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'])) {
    $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
  }
  $form['form_build_id'] = [
    '#type' => 'hidden',
    '#value' => $form['#build_id'],
    '#id' => $form['#build_id'],
    '#name' => 'form_build_id',
    // Form processing and validation require this value. Ensure the
    // submitted form value appears literally, regardless of custom #tree
    // and #parents being set elsewhere.
    '#parents' => [
      'form_build_id',
    ],
    // 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',
    ],
  ];

  // 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.
  // 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']);
  }
  else {
    $form['#cache']['contexts'][] = 'user.roles:authenticated';
    if ($user && $user
      ->isAuthenticated()) {

      // Generate a public token and placeholder based on the form ID.
      $placeholder = 'form_token_placeholder_' . Crypt::hashBase64($form_id);
      $form['#token'] = $placeholder;
      $form['form_token'] = [
        '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'),
        '#type' => 'token',
        '#default_value' => $placeholder,
        // Form processing and validation require this value. Ensure the
        // submitted form value appears literally, regardless of custom #tree
        // and #parents being set elsewhere.
        '#parents' => [
          'form_token',
        ],
        // 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 to ensure forms with a CSRF token
        // don't have poor cacheability.
        '#attached' => [
          'placeholders' => [
            $placeholder => [
              '#lazy_builder' => [
                'form_builder:renderFormTokenPlaceholder',
                [
                  $placeholder,
                ],
              ],
            ],
          ],
        ],
        '#cache' => [
          'max-age' => 0,
        ],
      ];
    }
  }
  if (isset($form_id)) {
    $form['form_id'] = [
      '#type' => 'hidden',
      '#value' => $form_id,
      '#id' => Html::getUniqueId("edit-{$form_id}"),
      // Form processing and validation require this value. Ensure the
      // submitted form value appears literally, regardless of custom #tree
      // and #parents being set elsewhere.
      '#parents' => [
        'form_id',
      ],
    ];
  }
  if (!isset($form['#id'])) {
    $form['#id'] = Html::getUniqueId($form_id);

    // Provide a selector usable by JavaScript. As the ID is unique, it's not
    // possible to rely on it in JavaScript.
    $form['#attributes']['data-drupal-selector'] = Html::getId($form_id);
  }
  $form += $this->elementInfo
    ->getInfo('form');
  $form += [
    '#tree' => FALSE,
    '#parents' => [],
  ];
  $form['#validate'][] = '::validateForm';
  $form['#submit'][] = '::submitForm';
  $build_info = $form_state
    ->getBuildInfo();

  // If no #theme has been set, automatically apply theme suggestions.
  // 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.
  if (!isset($form['#theme'])) {
    $form['#theme'] = [
      $form_id,
    ];
    if (isset($build_info['base_form_id'])) {
      $form['#theme'][] = $build_info['base_form_id'];
    }
  }

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