You are here

protected function FormBuilder::handleInputElement in Drupal 10

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

Adds the #name and #value properties of an input element before rendering.

File

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

Class

FormBuilder
Provides form building and processing.

Namespace

Drupal\Core\Form

Code

protected function handleInputElement($form_id, &$element, FormStateInterface &$form_state) {
  if (!isset($element['#name'])) {
    $name = array_shift($element['#parents']);
    $element['#name'] = $name;
    if ($element['#type'] == 'file') {

      // To make it easier to handle files, we place all
      // file fields in the 'files' array. Also, we do not support
      // nested file names.
      // @todo Remove this files prefix now?
      $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.
  $process_input = empty($element['#disabled']) && !in_array($element['#type'], [
    'item',
    'value',
  ], TRUE) && ($form_state
    ->isProgrammed() && $form_state
    ->isBypassingProgrammedAccessChecks() || $form_state
    ->isProcessingInput() && (!isset($element['#access']) || ($element['#access'] instanceof AccessResultInterface && $element['#access']
    ->isAllowed() || $element['#access'] === TRUE)));

  // Set the element's #value property.
  if (!isset($element['#value']) && !array_key_exists('#value', $element)) {

    // @todo Once all elements are converted to plugins in
    //   https://www.drupal.org/node/2311393, rely on
    //   $element['#value_callback'] directly.
    $value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
    if (!is_callable($value_callable)) {
      $value_callable = '\\Drupal\\Core\\Render\\Element\\FormElement::valueCallback';
    }
    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
        ->getUserInput(), $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 FormState::$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 FormState::$input for them.
      if (!$input_exists && !$form_state
        ->isRebuilding() && !$form_state
        ->isProgrammed()) {

        // Add the necessary parent keys to FormState::$input and sets the
        // element's input value to NULL.
        NestedArray::setValue($form_state
          ->getUserInput(), $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) {

        // Skip all value callbacks except safe ones like text if the CSRF
        // token was invalid.
        if (!$form_state
          ->hasInvalidToken() || $this
          ->valueCallableIsSafe($value_callable)) {
          $element['#value'] = call_user_func_array($value_callable, [
            &$element,
            $input,
            &$form_state,
          ]);
        }
        else {
          $input = NULL;
        }
        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.
      $element['#value'] = call_user_func_array($value_callable, [
        &$element,
        FALSE,
        &$form_state,
      ]);

      // 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'] = $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
  // \Drupal\Core\Form\FormState::cleanValues(). 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)) {
      $form_state
        ->setTriggeringElement($element);
    }

    // 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
      // \Drupal\Core\Form\FormState::cleanValues() and for the
      // self::doBuildForm() code that handles a form submission containing no
      // button information in \Drupal::request()->request.
      $buttons = $form_state
        ->getButtons();
      $buttons[] = $element;
      $form_state
        ->setButtons($buttons);
      if ($this
        ->buttonWasClicked($element, $form_state)) {
        $form_state
          ->setTriggeringElement($element);
      }
    }
  }

  // Set the element's value in $form_state->getValues(), but only, if its key
  // does not exist yet (a #value_callback may have already populated it).
  if (!NestedArray::keyExists($form_state
    ->getValues(), $element['#parents'])) {
    $form_state
      ->setValueForElement($element, $element['#value']);
  }
}