/** * Button #submit callback: Triggers submission of element forms. * * @param array $form * The form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. */ public static function trigger($form, FormStateInterface $form_state) { $triggered_element = $form_state->getTriggeringElement(); if (!empty($triggered_element['#ief_submit_trigger_all'])) { // The parent form was submitted, process all IEFs and their children. static::doSubmit($form, $form_state); } else { // A specific element was submitted, process it and all of its children. $array_parents = $triggered_element['#array_parents']; $array_parents = array_slice($array_parents, 0, -2); $element = NestedArray::getValue($form, $array_parents); static::doSubmit($element, $form_state); } }
/** * Ajax callback to render a sample of the input date format. * * @param array $form * Form API array structure. * @param \Drupal\Core\Form\FormStateInterface $form_state * Form state information. * * @return AjaxResponse * Ajax response with the rendered sample date using the given format. If * the given format cannot be identified or was empty, the response will * be empty as well. */ public static function ajaxSample(array $form, FormStateInterface $form_state) { $response = new AjaxResponse(); $format_value = NestedArray::getValue($form_state->getValues(), $form_state->getTriggeringElement()['#array_parents']); if (!empty($format_value)) { // Format the date with a custom date format with the given pattern. // The object is not instantiated in an Ajax context, so $this->t() // cannot be used here. $format = t('Displayed as %date_format', array('%date_format' => \Drupal::service('date.formatter')->format(REQUEST_TIME, 'custom', $format_value))); // Return a command instead of a string, since the Ajax framework // automatically prepends an additional empty DIV element for a string, // which breaks the layout. $response->addCommand(new ReplaceCommand('#edit-date-format-suffix', '<small id="edit-date-format-suffix">' . $format . '</small>')); } return $response; }
/** * Implements #element_validate callback for self::fieldSettingsForm(). */ public static function fieldSettingsFormValidate(array $element, FormStateInterface $form_state) { $add_more_button_form_parents = array_merge($element['#array_parents'], ['line_items', 'add_more', 'add']); // Only set the field settings as a value when it is not the "Add more" // button that has been clicked. $triggering_element = $form_state->getTriggeringElement(); if ($triggering_element['#array_parents'] != $add_more_button_form_parents) { $values = $form_state->getValues(); $values = NestedArray::getValue($values, $element['#array_parents']); $line_items_data = []; foreach (PaymentLineItemsInput::getLineItems($element['line_items'], $form_state) as $line_item) { $line_items_data[] = ['plugin_id' => $line_item->getPluginId(), 'plugin_configuration' => $line_item->getConfiguration()]; } $value = ['currency_code' => $values['currency_code'], 'line_items_data' => $line_items_data]; $form_state->setValueForElement($element, $value); } }
/** * Ajax callback to remove a field collection from a multi-valued field. * * @param array $form * @param \Drupal\Core\Form\FormStateInterface $form_state * * @return \Drupal\Core\Ajax\AjaxResponse * An AjaxResponse object. */ function ajaxRemove(array $form, FormStateInterface &$form_state) { // Process user input. $form and $form_state are modified in the process. //\Drupal::formBuilder()->processForm($form['#form_id'], $form, $form_state); // Retrieve the element to be rendered. $trigger = $form_state->getTriggeringElement(); $form_parents = explode('/', $trigger['#ajax']['options']['query']['element_parents']); $address = array_slice($form_parents, 0, -1); $form = NestedArray::getValue($form, $address); $status_messages = array('#theme' => 'status_messages'); $renderer = \Drupal::service('renderer'); $form['#prefix'] = empty($form['#prefix']) ? $renderer->render($status_messages) : $form['#prefix'] . $renderer->render($status_messages); $output = $renderer->render($form); drupal_process_attached($form); // TODO: Preserve javascript. See https://www.drupal.org/node/2502743 . $response = new AjaxResponse(); return $response->addCommand(new ReplaceCommand(NULL, $output)); }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, FilterFormat $filter_format = NULL) { // Add AJAX support. $form['#prefix'] = '<div id="video-embed-dialog-form">'; $form['#suffix'] = '</div>'; // Ensure relevant dialog libraries are attached. $form['#attached']['library'][] = 'editor/drupal.editor.dialog'; // Simple URL field and submit button for video URL. $form['video_url'] = ['#type' => 'textfield', '#title' => $this->t('Video URL'), '#required' => TRUE, '#default_value' => $this->getUserInput($form_state, 'video_url')]; // If no settings are found, use the defaults configured in the filter // formats interface. $settings = $this->getUserInput($form_state, 'settings'); if (empty($settings) && ($editor = Editor::load($filter_format->id()))) { $editor_settings = $editor->getSettings(); $plugin_settings = NestedArray::getValue($editor_settings, ['plugins', 'video_embed', 'defaults', 'children']); $settings = $plugin_settings ? $plugin_settings : []; } // Create a settings form from the existing video formatter. $form['settings'] = Video::mockInstance($settings)->settingsForm([], new FormState()); $form['settings']['#type'] = 'fieldset'; $form['settings']['#title'] = $this->t('Settings'); $form['actions'] = ['#type' => 'actions']; $form['actions']['save_modal'] = ['#type' => 'submit', '#value' => $this->t('Save'), '#submit' => [], '#ajax' => ['callback' => '::ajaxSubmit', 'event' => 'click', 'wrapper' => 'video-embed-dialog-form']]; return $form; }
public function validateForm(array &$element, array &$form_state, \Payment $payment) { $values = \Drupal\Component\Utility\NestedArray::getValue($form_state['values'], $element['#parents']); if (!empty($values['send_transfer_form'])) { $payment->method_data['send_transfer_form'] = $values['send_transfer_form']; } }
/** * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { $new_value = $value; if (is_array($value)) { if (!$value) { throw new MigrateException('Can not lookup without a value.'); } } else { $new_value = array($value); } $new_value = NestedArray::getValue($this->configuration['map'], $new_value, $key_exists); if (!$key_exists) { if (array_key_exists('default_value', $this->configuration)) { if (!empty($this->configuration['bypass'])) { throw new MigrateException('Setting both default_value and bypass is invalid.'); } return $this->configuration['default_value']; } if (empty($this->configuration['bypass'])) { throw new MigrateSkipRowException(); } else { return $value; } } return $new_value; }
/** * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) { $editor_settings = $editor->getSettings(); $plugin_settings = NestedArray::getValue($editor_settings, ['plugins', 'video_embed', 'defaults', 'children']); $settings = $plugin_settings ?: []; $form['defaults'] = ['#title' => $this->t('Default Settings'), '#type' => 'fieldset', '#tree' => TRUE, 'children' => Video::mockInstance($settings)->settingsForm([], new FormState())]; return $form; }
public function validateForm(array &$element, array &$form_state, \Payment $payment) { $values = \Drupal\Component\Utility\NestedArray::getValue($form_state['values'], $element['#parents']); $this->validateValues($element, $values); // Merge in validated fields. foreach (array('issuer', 'credit_card_number', 'secure_code', 'expiry_date') as $key) { $payment->method_data[$key] = $values[$key]; } }
/** * Listener for migration imports. */ public function onMigrateImport(MigrateImportEvent $event) { $migration = $event->getMigration(); $configuration = $migration->getDestinationConfiguration(); $entity_types = NestedArray::getValue($configuration, ['content_translation_update_definitions']); if ($entity_types) { $entity_types = array_intersect_key($this->entityManager->getDefinitions(), array_flip($entity_types)); $this->updateDefinitions($entity_types); } }
/** * {@inheritdoc} */ public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { $massaged_values = []; foreach ($values as $delta => $item_values) { $element = NestedArray::getValue($form, array_slice($item_values['array_parents'], count($form['#array_parents']))); $plugin_selector = static::getPluginSelector($form_state, $element); $plugin_selector->submitSelectorForm($element['plugin_selector'], $form_state); $massaged_values[$delta] = ['plugin_instance' => $plugin_selector->getSelectedPlugin()]; } return $massaged_values; }
/** * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { if (!is_array($value)) { throw new MigrateException('Input should be an array.'); } $new_value = NestedArray::getValue($value, $this->configuration['index'], $key_exists); if (!$key_exists) { throw new MigrateException('Array index missing, extraction failed.'); } return $new_value; }
/** * Processes AJAX file uploads and deletions. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. * * @return \Drupal\Core\Ajax\AjaxResponse * An AjaxResponse object. */ public function upload(Request $request) { $form_parents = explode('/', $request->query->get('element_parents')); $form_build_id = $request->query->get('form_build_id'); $request_form_build_id = $request->request->get('form_build_id'); if (empty($request_form_build_id) || $form_build_id !== $request_form_build_id) { // Invalid request. drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error'); $response = new AjaxResponse(); $status_messages = array('#theme' => 'status_messages'); return $response->addCommand(new ReplaceCommand(NULL, drupal_render($status_messages))); } try { /** @var $ajaxForm \Drupal\system\FileAjaxForm */ $ajaxForm = $this->getForm($request); $form = $ajaxForm->getForm(); $form_state = $ajaxForm->getFormState(); $commands = $ajaxForm->getCommands(); } catch (HttpExceptionInterface $e) { // Invalid form_build_id. drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error'); $response = new AjaxResponse(); $status_messages = array('#theme' => 'status_messages'); return $response->addCommand(new ReplaceCommand(NULL, drupal_render($status_messages))); } // Get the current element and count the number of files. $current_element = NestedArray::getValue($form, $form_parents); $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0; // Process user input. $form and $form_state are modified in the process. drupal_process_form($form['#form_id'], $form, $form_state); // Retrieve the element to be rendered. $form = NestedArray::getValue($form, $form_parents); // Add the special Ajax class if a new file was added. if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; } else { $form['#suffix'] .= '<span class="ajax-new-content"></span>'; } $status_messages = array('#theme' => 'status_messages'); $form['#prefix'] .= drupal_render($status_messages); $output = drupal_render($form); drupal_process_attached($form); $js = _drupal_add_js(); $settings = drupal_merge_js_settings($js['settings']['data']); $response = new AjaxResponse(); foreach ($commands as $command) { $response->addCommand($command, TRUE); } return $response->addCommand(new ReplaceCommand(NULL, $output, $settings)); }
/** * Global #process callback for form elements. * * @param array $element * The element render array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The altered element array. * * @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter */ public static function process(array $element, FormStateInterface $form_state, array &$complete_form) { if (!empty($element['#bootstrap_ignore_process'])) { return $element; } static $theme; if (!isset($theme)) { $theme = Bootstrap::getTheme(); } $e = Element::create($element, $form_state); // Add "form-inline" class. if ($e->hasClass('container-inline')) { $e->replaceClass('container-inline', 'form-inline'); } if ($e->isType(['color', 'date', 'number', 'password', 'password_confirm', 'range', 'tel', 'weight'])) { $e->addClass('form-inline', 'wrapper_attributes'); } // Add "form-group" class, don't replace "form-wrapper" as that is needed // by some JavaScript for certain functionality to work. if ($e->hasClass('form-wrapper')) { $e->addClass('form-group'); } // Check for errors and set the "has_error" property flag. $errors = $e->getError(); $e->setProperty('errors', $errors); if (isset($errors) || $e->getProperty('required') && $theme->getSetting('forms_required_has_error')) { $e->setProperty('has_error', TRUE); } // Automatically inject the nearest button found after this element if // #input_group_button exists. if ($e->getProperty('input_group_button')) { // Obtain the parent array to limit search. $array_parents = $e->getProperty('array_parents', []); // Remove the current element from the array. array_pop($array_parents); // If element is nested, return the referenced parent from the form. // Otherwise return the complete form. $parent = Element::create($array_parents ? NestedArray::getValue($complete_form, $array_parents) : $complete_form, $form_state); // Find the closest button. if ($button = self::findButton($parent)) { $e->setProperty('field_suffix', $button->setIcon()->getArray()); $button->setProperty('access', FALSE); } } return $element; }
/** * Get all system variables * * @return array() */ public function getVariablesData() { $data = array(); $variables = array('acquia_spi_send_node_user', 'acquia_spi_admin_priv', 'acquia_spi_module_diff_data', 'acquia_spi_send_watchdog', 'acquia_spi_use_cron', 'cache_backends', 'cache_default_class', 'cache_inc', 'cron_safe_threshold', 'googleanalytics_cache', 'error_level', 'preprocess_js', 'page_cache_maximum_age', 'block_cache', 'preprocess_css', 'page_compression', 'cache', 'cache_lifetime', 'cron_last', 'clean_url', 'redirect_global_clean', 'theme_zen_settings', 'site_offline', 'site_name', 'user_register', 'user_signatures', 'user_admin_role', 'user_email_verification', 'user_cancel_method', 'filter_fallback_format', 'dblog_row_limit', 'date_default_timezone', 'file_default_scheme', 'install_profile', 'maintenance_mode', 'update_last_check', 'site_default_country', 'acquia_spi_saved_variables', 'acquia_spi_set_variables_automatic', 'acquia_spi_ignored_set_variables', 'acquia_spi_set_variables_override'); $allConfigData = self::getAllConfigs(); $spi_def_vars = \Drupal::config('acquia_connector.settings')->get('spi.def_vars'); $waived_spi_def_vars = \Drupal::config('acquia_connector.settings')->get('spi.def_waived_vars'); // Merge hard coded $variables with vars from SPI definition. foreach ($spi_def_vars as $var_name => $var) { if (!in_array($var_name, $waived_spi_def_vars) && !in_array($var_name, $variables)) { $variables[] = $var_name; } } // @todo Add comment settings for node types. foreach ($variables as $name) { if (!empty($this->mapping[$name])) { // state if ($this->mapping[$name][0] == 'state' and !empty($this->mapping[$name][1])) { $data[$name] = \Drupal::state()->get($this->mapping[$name][1]); } elseif ($this->mapping[$name][0] == 'settings' and !empty($this->mapping[$name][1])) { $data[$name] = Settings::get($this->mapping[$name][1]); } else { $key_exists = NULL; $value = Utility\NestedArray::getValue($allConfigData, $this->mapping[$name], $key_exists); if ($key_exists) { $data[$name] = $value; } else { $data[$name] = 0; } } } else { // @todo: Implement D8 way to update variables mapping. $data[$name] = 'Variable not implemented.'; } } // Unset waived vars so they won't be sent to NSPI. foreach ($data as $var_name => $var) { if (in_array($var_name, $waived_spi_def_vars)) { unset($data[$var_name]); } } // Collapse to JSON string to simplify transport. return Json::encode($data); }
public function validateForm(array &$element, array &$form_state, \Payment $payment) { $values =& \Drupal\Component\Utility\NestedArray::getValue($form_state['values'], $element['#parents']); $method_data =& $payment->method_data; $method_data['holder'] = $values['holder']; if (empty($values['holder']) == TRUE) { form_error($element['holder'], t('Please enter the name of the account holder.')); } $method_data['iban'] = trim($values['ibanbic']['iban']); $method_data['bic'] = trim($values['ibanbic']['bic']); $method_data['country'] = substr($method_data['iban'], 0, 2); require_once dirname(__FILE__) . '/../php-iban.php'; if (verify_iban($method_data['iban']) == FALSE) { form_error($element['ibanbic']['iban'], t('Please enter a valid IBAN.')); } if (preg_match('/^[a-z]{6}[2-9a-z][0-9a-np-z](|xxx|[0-9a-wyz][0-9a-z]{2})$/i', $method_data['bic']) != 1) { form_error($element['ibanbic']['bic'], t('Please enter a valid BIC.')); } }
/** * Tests getting nested array values. * * @covers ::getValue */ public function testGetValue() { // Verify getting a value of a nested element. $value = NestedArray::getValue($this->form, $this->parents); $this->assertSame('Nested element', $value['#value'], 'Nested element value found.'); // Verify changing a value of a nested element by reference. $value =& NestedArray::getValue($this->form, $this->parents); $value['#value'] = 'New value'; $value = NestedArray::getValue($this->form, $this->parents); $this->assertSame('New value', $value['#value'], 'Nested element value was changed by reference.'); $this->assertSame('New value', $this->form['details']['element']['#value'], 'Nested element value was changed by reference.'); // Verify that an existing key is reported back. $key_exists = NULL; NestedArray::getValue($this->form, $this->parents, $key_exists); $this->assertTrue($key_exists, 'Existing key found.'); // Verify that a non-existing key is reported back and throws no errors. $key_exists = NULL; $parents = $this->parents; $parents[] = 'foo'; NestedArray::getValue($this->form, $parents, $key_exists); $this->assertFalse($key_exists, 'Non-existing key not found.'); }
/** * Processes form submissions. * * From "Basic Form Generation and Processing in Drupal 8", chapter 4. */ public function submitForm(array &$form, FormStateInterface $form_state) { // Extract the values submitted by the user. $values = $form_state->getValues(); $name = $values['first_name']; // Values you stored in the form array are also available. $info = $values['information']; // Get another value, using a method where it could be nested. $parents = $form['company']['#array_parents']; $company = NestedArray::getValue($values, $parents); // Processing code would go here. As a proxy, display a message with the // values. Note that since the values are not sanitized, insert them // into t() with @variable. If inserting into the database, do not // sanitize. if ($company) { drupal_set_message($this->t('Thank you @name from @company', array('@name' => $name, '@company' => $company))); } else { drupal_set_message($this->t('Thank you @name', array('@name' => $name))); } // Make sure the form is rebuilt properly for Ajax. $form_state->setRebuild(); }
/** * Validate helper to have support for other entity reference widgets. * * @param $element * @param FormStateInterface $form_state * @param $form */ public static function targetTypeValidate($element, FormStateInterface $form_state, $form) { $values =& $form_state->getValues(); $element_values = NestedArray::getValue($values, $element['#parents']); $bundle_options = array(); if ($element_values) { $enabled = 0; foreach ($element_values as $machine_name => $bundle_info) { if (isset($bundle_info['enabled']) && $bundle_info['enabled']) { $bundle_options[$machine_name] = $machine_name; $enabled++; } } // All disabled = all enabled. if ($enabled === 0) { $bundle_options = NULL; } } // New value parents. $parents = array_merge(array_slice($element['#parents'], 0, -1), array('target_bundles')); NestedArray::setValue($values, $parents, $bundle_options); }
/** * Special submit handling. */ public function submitOptionsForm(&$form, FormStateInterface $form_state) { $element = array('#parents' => array('query', 'options', 'query_tags')); $value = explode(',', NestedArray::getValue($form_state->getValues(), $element['#parents'])); $value = array_filter(array_map('trim', $value)); $form_state->setValueForElement($element, $value); }
/** * Form submission handler for upload/remove button of formElement(). * * This runs in addition to and after file_managed_file_submit(). * * @see file_managed_file_submit() */ public static function submit($form, FormStateInterface $form_state) { // During the form rebuild, formElement() will create field item widget // elements using re-indexed deltas, so clear out FormState::$input to // avoid a mismatch between old and new deltas. The rebuilt elements will // have #default_value set appropriately for the current state of the field, // so nothing is lost in doing this. $button = $form_state->getTriggeringElement(); $parents = array_slice($button['#parents'], 0, -2); NestedArray::setValue($form_state->getUserInput(), $parents, NULL); // Go one level up in the form, to the widgets container. $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); $field_name = $element['#field_name']; $parents = $element['#field_parents']; $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2)); foreach ($submitted_values as $delta => $submitted_value) { if (empty($submitted_value['fids'])) { unset($submitted_values[$delta]); } } // If there are more files uploaded via the same widget, we have to separate // them, as we display each file in it's own widget. $new_values = array(); foreach ($submitted_values as $delta => $submitted_value) { if (is_array($submitted_value['fids'])) { foreach ($submitted_value['fids'] as $fid) { $new_value = $submitted_value; $new_value['fids'] = array($fid); $new_values[] = $new_value; } } else { $new_value = $submitted_value; } } // Re-index deltas after removing empty items. $submitted_values = array_values($new_values); // Update form_state values. NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values); // Update items. $field_state = static::getWidgetState($parents, $field_name, $form_state); $field_state['items'] = $submitted_values; static::setWidgetState($parents, $field_name, $form_state, $field_state); }
/** * {@inheritdoc} */ public function getConfigurationItem($key) { $configuration = $this->getConfiguration(); return NestedArray::getValue($configuration, (array) $key); }
/** * Submit handler for the menu overview form. * * This function takes great care in saving parent items first, then items * underneath them. Saving items in the incorrect order can break the tree. */ protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) { // Form API supports constructing and validating self-contained sections // within forms, but does not allow to handle the form section's submission // equally separated yet. Therefore, we use a $form_state key to point to // the parents of the form section. $parents = $form_state->get('menu_overview_form_parents'); $input = NestedArray::getValue($form_state->getUserInput(), $parents); $form =& NestedArray::getValue($complete_form, $parents); // When dealing with saving menu items, the order in which these items are // saved is critical. If a changed child item is saved before its parent, // the child item could be saved with an invalid path past its immediate // parent. To prevent this, save items in the form in the same order they // are sent, ensuring parents are saved first, then their children. // See https://www.drupal.org/node/181126#comment-632270. $order = is_array($input) ? array_flip(array_keys($input)) : array(); // Update our original form with the new order. $form = array_intersect_key(array_merge($order, $form), $form); $fields = array('weight', 'parent', 'enabled'); $form_links = $form['links']; foreach (Element::children($form_links) as $id) { if (isset($form_links[$id]['#item'])) { $element = $form_links[$id]; $updated_values = array(); // Update any fields that have changed in this menu item. foreach ($fields as $field) { if ($element[$field]['#value'] != $element[$field]['#default_value']) { $updated_values[$field] = $element[$field]['#value']; } } if ($updated_values) { // Use the ID from the actual plugin instance since the hidden value // in the form could be tampered with. $this->menuLinkManager->updateDefinition($element['#item']->link->getPLuginId(), $updated_values); } } } }
/** * {@inheritdoc} */ public function getSetting($name) { if (is_array($name)) { if (NestedArray::keyExists($this->settings, $name)) { return NestedArray::getValue($this->settings, $name); } elseif ($plugin = $this->getPlugin()) { $defaults = $plugin->defaultSettings(); return NestedArray::getValue($defaults, $name); } } else { if (isset($this->settings[$name])) { return $this->settings[$name]; } elseif ($plugin = $this->getPlugin()) { $defaults = $plugin->defaultSettings(); if (isset($defaults[$name])) { return $defaults[$name]; } } } }
/** * Gets data from this configuration object. * * @param string $key * A string that maps to a key within the configuration data. * For instance in the following configuration array: * @code * array( * 'foo' => array( * 'bar' => 'baz', * ), * ); * @endcode * A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo' * would return array('bar' => 'baz'). * If no key is specified, then the entire data array is returned. * * @return mixed * The data that was requested. */ public function get($key = '') { if (empty($key)) { return $this->data; } else { $parts = explode('.', $key); if (count($parts) == 1) { return isset($this->data[$key]) ? $this->data[$key] : NULL; } else { $value = NestedArray::getValue($this->data, $parts, $key_exists); return $key_exists ? $value : NULL; } } }
/** * Processes elements that have input groups. * * @param \Drupal\bootstrap\Utility\Element $element * The element object. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. */ protected static function processInputGroups(Element $element, FormStateInterface $form_state, array &$complete_form) { // Automatically inject the nearest button found after this element if // #input_group_button exists. if ($element->getProperty('input_group_button')) { // Obtain the parent array to limit search. $array_parents = $element->getProperty('array_parents', []); // Remove the current element from the array. array_pop($array_parents); // Retrieve the parent element. $parent = Element::create(NestedArray::getValue($complete_form, $array_parents), $form_state); // Find the closest button. if ($button = self::findButton($parent)) { $element->appendProperty('field_suffix', $button->setIcon()); $button->setProperty('access', FALSE); } } $input_group_attributes = ['class' => ['input-group-' . ($element->getProperty('input_group_button') ? 'btn' : 'addon')]]; if ($prefix = $element->getProperty('field_prefix')) { $element->setProperty('field_prefix', ['#type' => 'html_tag', '#tag' => 'span', '#attributes' => $input_group_attributes, '#value' => Element::create($prefix)->render(), '#weight' => -1]); } if ($suffix = $element->getProperty('field_suffix')) { $element->setProperty('field_suffix', ['#type' => 'html_tag', '#tag' => 'span', '#attributes' => $input_group_attributes, '#value' => Element::create($suffix)->render(), '#weight' => 1]); } }
/** * Adds the #name and #value properties of an input element before rendering. */ 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 in file.inc, 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']) && ($form_state->isProgrammed() && $form_state->isBypassingProgrammedAccessChecks() || $form_state->isProcessingInput() && (!isset($element['#access']) || $element['#access'])); // 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, array(&$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, array(&$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'] = 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 // \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']); } }
/** * Extracts the layout settings form and form state from the full form. * * @param array $form * Full form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * Full form state. * * @return array * An array with two values: the new form array and form state object. */ protected function getLayoutSettingsForm(array &$form, FormStateInterface $form_state) { $layout_settings_form = NestedArray::getValue($form, array_merge($form['#variant_array_parents'], ['layout_settings_wrapper', 'layout_settings'])); $layout_settings_form_state = (new FormState())->setValues($form_state->getValue('layout_settings')); return [$layout_settings_form, $layout_settings_form_state]; }
/** * Render API callback: gets the layout settings elements. */ public static function formatterSettingsAjaxCallback(array $form, FormStateInterface $form_state) { $formatter_array_parents = $form['#formatter_array_parents']; return NestedArray::getValue($form, array_merge($formatter_array_parents, ['formatter_settings_wrapper'])); }
/** * Gets the current value of a #select element, from within a form constructor function. * * This function is intended for use in highly dynamic forms (in particular the * add view wizard) which are rebuilt in different ways depending on which * triggering element (AJAX or otherwise) was most recently fired. For example, * sometimes it is necessary to decide how to build one dynamic form element * based on the value of a different dynamic form element that may not have * even been present on the form the last time it was submitted. This function * takes care of resolving those conflicts and gives you the proper current * value of the requested #select element. * * By necessity, this function sometimes uses non-validated user input from * FormState::$input in making its determination. Although it performs some * minor validation of its own, it is not complete. The intention is that the * return value of this function should only be used to help decide how to * build the current form the next time it is reloaded, not to be saved as if * it had gone through the normal, final form validation process. Do NOT use * the results of this function for any other purpose besides deciding how to * build the next version of the form. * * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param $parents * An array of parent keys that point to the part of the submitted form * values that are expected to contain the element's value (in the case where * this form element was actually submitted). In a simple case (assuming * #tree is TRUE throughout the form), if the select element is located in * $form['wrapper']['select'], so that the submitted form values would * normally be found in $form_state->getValue(array('wrapper', 'select')), * you would pass array('wrapper', 'select') for this parameter. * @param $default_value * The default value to return if the #select element does not currently have * a proper value set based on the submitted input. * @param $element * An array representing the current version of the #select element within * the form. * * @return * The current value of the #select element. A common use for this is to feed * it back into $element['#default_value'] so that the form will be rendered * with the correct value selected. */ public static function getSelected(FormStateInterface $form_state, $parents, $default_value, $element) { // For now, don't trust this to work on anything but a #select element. if (!isset($element['#type']) || $element['#type'] != 'select' || !isset($element['#options'])) { return $default_value; } // If there is a user-submitted value for this element that matches one of // the currently available options attached to it, use that. We need to check // FormState::$input rather than $form_state->getValues() here because the // triggering element often has the #limit_validation_errors property set to // prevent unwanted errors elsewhere on the form. This means that the // $form_state->getValues() array won't be complete. We could make it complete // by adding each required part of the form to the #limit_validation_errors // property individually as the form is being built, but this is difficult to // do for a highly dynamic and extensible form. This method is much simpler. $user_input =& $form_state->getUserInput(); if (!empty($user_input)) { $key_exists = NULL; $submitted = NestedArray::getValue($user_input, $parents, $key_exists); // Check that the user-submitted value is one of the allowed options before // returning it. This is not a substitute for actual form validation; // rather it is necessary because, for example, the same select element // might have #options A, B, and C under one set of conditions but #options // D, E, F under a different set of conditions. So the form submission // might have occurred with option A selected, but when the form is rebuilt // option A is no longer one of the choices. In that case, we don't want to // use the value that was submitted anymore but rather fall back to the // default value. if ($key_exists && in_array($submitted, array_keys($element['#options']))) { return $submitted; } } // Fall back on returning the default value if nothing was returned above. return $default_value; }