public function FormBuilder::doBuildForm in Drupal 10
Same name and namespace in other branches
- 8 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::doBuildForm()
- 9 core/lib/Drupal/Core/Form/FormBuilder.php \Drupal\Core\Form\FormBuilder::doBuildForm()
File
- core/
lib/ Drupal/ Core/ Form/ FormBuilder.php, line 910
Class
- FormBuilder
- Provides form building and processing.
Namespace
Drupal\Core\FormCode
public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) {
// Initialize as unprocessed.
$element['#processed'] = FALSE;
// Use element defaults.
if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->elementInfo
->getInfo($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 += [
'#required' => FALSE,
'#attributes' => [],
'#title_display' => 'before',
'#description_display' => 'after',
'#errors' => NULL,
];
// Special handling if we're on the top level form element.
if (isset($element['#type']) && $element['#type'] == 'form') {
if (!empty($element['#https']) && !UrlHelper::isExternal($element['#action'])) {
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.
$form_state
->setCompleteForm($element);
// 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.
$input = $form_state
->getUserInput();
if ($form_state
->isProgrammed() || !empty($input) && (isset($input['form_id']) && $input['form_id'] == $form_id)) {
$form_state
->setProcessInput();
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);
// Ignore all submitted values.
$form_state
->setUserInput([]);
$request = $this->requestStack
->getCurrentRequest();
// Do not trust any POST data.
$request->request = new ParameterBag();
// Make sure file uploads do not get processed.
$request->files = new FileBag();
// Ensure PHP globals reflect these changes.
$request
->overrideGlobals();
}
}
}
else {
$form_state
->setProcessInput(FALSE);
}
// All form elements should have an #array_parents property.
$element['#array_parents'] = [];
}
if (!isset($element['#id'])) {
$unprocessed_id = 'edit-' . implode('-', $element['#parents']);
$element['#id'] = Html::getUniqueId($unprocessed_id);
// Provide a selector usable by JavaScript. As the ID is unique, it's 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, it's not
// possible to rely on it in JavaScript.
$element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']);
}
// 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 $callback) {
$complete_form =& $form_state
->getCompleteForm();
$element = call_user_func_array($form_state
->prepareCallback($callback), [
&$element,
&$form_state,
&$complete_form,
]);
}
$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;
if (isset($element['#access'])) {
$access = $element['#access'];
$inherited_access = NULL;
if ($access instanceof AccessResultInterface && !$access
->isAllowed() || $access === FALSE) {
$inherited_access = $access;
}
}
foreach (Element::children($element) as $key) {
// 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->elementInfo
->getInfo($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'];
}
// Children inherit #access from parent.
if (isset($inherited_access)) {
$element[$key]['#access'] = $inherited_access;
}
// Make child elements inherit their parent's #disabled and #allow_focus
// values unless they specify their own.
foreach ([
'#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'], [
$key,
]) : [
$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 $callback) {
$element = call_user_func_array($form_state
->prepareCallback($callback), [
$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') {
$form_state
->setHasFileElement();
}
// 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 ($form_state
->hasFileElement()) {
$element['#attributes']['enctype'] = 'multipart/form-data';
}
// 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;
// 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.
$buttons = $form_state
->getButtons();
if (!$form_state
->isProgrammed() && !$form_state
->getTriggeringElement() && !empty($buttons)) {
$form_state
->setTriggeringElement($buttons[0]);
}
$triggering_element = $form_state
->getTriggeringElement();
// 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.
if (isset($triggering_element['#validate'])) {
$form_state
->setValidateHandlers($triggering_element['#validate']);
}
if (isset($triggering_element['#submit'])) {
$form_state
->setSubmitHandlers($triggering_element['#submit']);
}
// If the triggering element executes submit handlers, then set the form
// state key that's needed for those handlers to run.
if (!empty($triggering_element['#executes_submit_callback'])) {
$form_state
->setSubmitted();
}
// Special processing if the triggering element is a button.
if (!empty($triggering_element['#is_button'])) {
// 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
// $form_state->getValue(BUTTON_NAME) being set. But it's common for
// forms to have several buttons named 'op' and switch on
// $form_state->getValue('op') during submit handler execution.
$form_state
->setValue($triggering_element['#name'], $triggering_element['#value']);
}
}
return $element;
}