/** * Clear values from upload form element. * * @param array $element * Upload form element. * @param \Drupal\Core\Form\FormStateInterface $form_state * Form state object. */ protected function clearFormValues(array &$element, FormStateInterface $form_state) { // We propagated entities to the other parts of the system. We can now remove // them from our values. $form_state->setValueForElement($element['upload']['fids'], ''); NestedArray::setValue($form_state->getUserInput(), $element['upload']['fids']['#parents'], ''); }
/** * Form validation handler for widget elements. * * @param array $element * The form element. * @param array $form_state * The form state. */ public static function validateElement(array $element, FormStateInterface $form_state) { parent::validateElement($element, $form_state); // Massage submitted form values. // Drupal\Core\Field\WidgetBase::submit() expects values as // an array of values keyed by delta first, then by column, while our // widgets return the opposite. if (is_array($element['#value'])) { $values = array_values($element['#value']); } else { $values = array($element['#value']); } // Filter out the 'none' option. Use a strict comparison, because // 0 == 'any string'. $index = array_search('_none', $values, TRUE); if ($index !== FALSE) { unset($values[$index]); } // Transpose selections from field => delta to delta => field. $items = array(); foreach ($values as $value) { $items[] = array($element['#key_column'] => $value); } NestedArray::setValue($form_state['values'], $element['#parents'], $items); }
/** * Tests force-setting values. * * @covers ::setValue */ public function testSetValueForce() { $new_value = array('one'); $this->form['details']['non-array-parent'] = 'string'; $parents = array('details', 'non-array-parent', 'child'); NestedArray::setValue($this->form, $parents, $new_value, TRUE); $this->assertSame($new_value, $this->form['details']['non-array-parent']['child'], 'The nested element was not forced to the new value.'); }
public function execute() { $file = $this->getUnaliasedPath($this->configuration['in']); $data = file_exists($file) ? YAML::decode(file_get_contents($file)) : []; $keys = explode('/', $this->configuration['key']); NestedArray::setValue($data, $keys, $this->configuration['value']); file_put_contents($file, YAML::encode($data)); }
/** * Form element validation callback for upload element on file widget. * * Checks if user has uploaded more files than allowed. * * This validator is used only when cardinality not set to 1 or unlimited. * * @param array $element * The element. * @param FormStateInterface $form_state * The form state. * @param array $form * The form. */ public static function validateMultipleCount($element, FormStateInterface $form_state, $form) { $parents = $element['#parents']; $values = NestedArray::getValue($form_state->getValues(), $parents); array_pop($parents); $current = count(Element::children(NestedArray::getValue($form, $parents))) - 1; $field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($element['#entity_type']); $field_storage = $field_storage_definitions[$element['#field_name']]; $uploaded = count($values['fids']); $count = $uploaded + $current; if ($count > $field_storage->getCardinality()) { $keep = $uploaded - $count + $field_storage->getCardinality(); $removed_files = array_slice($values['fids'], $keep); $removed_names = array(); foreach ($removed_files as $id) { /** @var \Drupal\embridge\EmbridgeAssetEntityInterface $asset */ $asset = EmbridgeAssetEntity::load($id); $removed_names[] = $asset->getFilename(); } $args = array('%field' => $field_storage->getName(), '@max' => $field_storage->getCardinality(), '@count' => $uploaded, '%list' => implode(', ', $removed_names)); $message = t('Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', $args); drupal_set_message($message, 'warning'); $values['fids'] = array_slice($values['fids'], 0, $keep); NestedArray::setValue($form_state->getValues(), $element['#parents'], $values); } }
/** * 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); }
/** * Submit callback to remove an item from the field UI multiple wrapper. * * When a remove button is submitted, we need to find the item that it * referenced and delete it. Since field UI has the deltas as a straight * unbroken array key, we have to renumber everything down. Since we do this * we *also* need to move all the deltas around in the $form_state->values * and $form_state input so that user changed values follow. This is a bit * of a complicated process. */ public static function removeSubmit($form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); $delta = $button['#delta']; // Where in the form we'll find the parent element. $address = array_slice($button['#array_parents'], 0, -4); // Go one level up in the form, to the widgets container. $parent_element = NestedArray::getValue($form, array_merge($address, array('widget'))); $field_name = $parent_element['#field_name']; $parents = $parent_element['#field_parents']; $field_state = static::getWidgetState($parents, $field_name, $form_state); // Go ahead and renumber everything from our delta to the last // item down one. This will overwrite the item being removed. for ($i = $delta; $i <= $field_state['items_count']; $i++) { $old_element_address = array_merge($address, array('widget', $i + 1)); $old_element_state_address = array_merge($address, array($i + 1)); $new_element_state_address = array_merge($address, array($i)); $moving_element = NestedArray::getValue($form, $old_element_address); $moving_element_value = NestedArray::getValue($form_state->getValues(), $old_element_state_address); $moving_element_input = NestedArray::getValue($form_state->getUserInput(), $old_element_state_address); // Tell the element where it's being moved to. $moving_element['#parents'] = $new_element_state_address; // Move the element around. $form_state->setValueForElement($moving_element, $moving_element_value); $user_input = $form_state->getUserInput(); NestedArray::setValue($user_input, $moving_element['#parents'], $moving_element_input); $form_state->setUserInput($user_input); // Move the entity in our saved state. if (isset($field_state['field_collection_item'][$i + 1])) { $field_state['field_collection_item'][$i] = $field_state['field_collection_item'][$i + 1]; } else { unset($field_state['field_collection_item'][$i]); } } // Replace the deleted entity with an empty one. This helps to ensure that // trying to add a new entity won't ressurect a deleted entity from the // trash bin. $count = count($field_state['field_collection_item']); $field_state['field_collection_item'][$count] = entity_create('field_collection_item', array('field_name' => $field_name)); // Then remove the last item. But we must not go negative. if ($field_state['items_count'] > 0) { $field_state['items_count']--; } // Fix the weights. Field UI lets the weights be in a range of // (-1 * item_count) to (item_count). This means that when we remove one, // the range shrinks; weights outside of that range then get set to // the first item in the select by the browser, floating them to the top. // We use a brute force method because we lost weights on both ends // and if the user has moved things around, we have to cascade because // if I have items weight weights 3 and 4, and I change 4 to 3 but leave // the 3, the order of the two 3s now is undefined and may not match what // the user had selected. $input = NestedArray::getValue($form_state->getUserInput(), $address); // Sort by weight. uasort($input, '_field_collection_sort_items_helper'); // Reweight everything in the correct order. $weight = -1 * $field_state['items_count']; foreach ($input as $key => $item) { if ($item) { $input[$key]['_weight'] = $weight++; } } $user_input = $form_state->getUserInput(); NestedArray::setValue($user_input, $address, $input); $form_state->setUserInput($user_input); static::setWidgetState($parents, $field_name, $form_state, $field_state); $form_state->setRebuild(); }
/** * {@inheritdoc} */ public function setValue($element, $value, &$form_state) { NestedArray::setValue($form_state['values'], $element['#parents'], $value, TRUE); }
/** * Sets destination properties. * * @param string $property * The name of the destination property. * @param mixed $value * The property value to set on the destination. */ public function setDestinationProperty($property, $value) { $this->rawDestination[$property] = $value; NestedArray::setValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property), $value, TRUE); }
/** * Implements \Drupal\Core\Form\FormStateInterface::setValue() */ public function setValue($key, $value) { NestedArray::setValue($this->getValues(), (array) $key, $value, TRUE); return $this; }
/** * Updates a (possible nested) entity property with a value. * * @param \Drupal\Core\Entity\EntityInterface $entity * The config entity. * @param array $parents * The array of parents. * @param string|object $value * The value to update to. */ protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) { $top_key = array_shift($parents); $entity_value = $entity->get($top_key); if (is_array($entity_value)) { NestedArray::setValue($entity_value, $parents, $value); } else { $entity_value = $value; } $entity->set($top_key, $entity_value); }
/** * {@inheritdoc} */ public function updateData($key, $values = array(), $replace = FALSE) { if ($replace) { if (!is_array($this->unserializedData)) { $this->unserializedData = unserialize($this->get('data')->value); if (!is_array($this->unserializedData)) { $this->unserializedData = array(); } } NestedArray::setValue($this->unserializedData, \Drupal::service('tmgmt.data')->ensureArrayKey($key), $values); } foreach ($values as $index => $value) { // In order to preserve existing values, we can not aplly the values array // at once. We need to apply each containing value on its own. // If $value is an array we need to advance the hierarchy level. if (is_array($value)) { $this->updateData(array_merge(\Drupal::service('tmgmt.data')->ensureArrayKey($key), array($index)), $value); } else { if (!is_array($this->unserializedData)) { $this->unserializedData = unserialize($this->get('data')->value); } NestedArray::setValue($this->unserializedData, array_merge(\Drupal::service('tmgmt.data')->ensureArrayKey($key), array($index)), $value); } } }
/** * Converts a flattened data structure into a nested array. * * This function can be used by translators to help with the data conversion. * * Nested keys will be created based on the colon, so for example * $flattened_data['key1][key2][key3'] will be converted into * $data['key1']['key2']['key3']. * * @param array $flattened_data * The flattened data array. * * @return array * The nested data array. */ public function unflatten(array $flattened_data) { $data = array(); foreach ($flattened_data as $key => $flattened_data_entry) { NestedArray::setValue($data, explode(static::TMGMT_ARRAY_DELIMITER, $key), $flattened_data_entry); } return $data; }
/** * {@inheritdoc} */ public function setValueForElement($element, $value) { $values = $this->getValues(); NestedArray::setValue($values, $element['#parents'], $value, TRUE); $this->set('values', $values); return $this; }
/** * {@inheritdoc} */ public static function setWidgetState(array $parents, $field_name, array &$form_state, array $field_state) { NestedArray::setValue($form_state, static::getWidgetStateParents($parents, $field_name), $field_state); }
/** * Clears the country-specific form values when the country changes. * * Implemented as an #after_build callback because #after_build runs before * validation, allowing the values to be cleared early enough to prevent the * "Illegal choice" error. */ public static function clearValues(array $element, FormStateInterface $form_state) { $triggering_element = $form_state->getTriggeringElement(); if (!$triggering_element) { return $element; } $triggering_element_name = end($triggering_element['#parents']); if ($triggering_element_name == 'country_code') { $keys = ['dependent_locality', 'locality', 'administrative_area']; $input =& $form_state->getUserInput(); foreach ($keys as $key) { $parents = array_merge($element['#parents'], [$key]); NestedArray::setValue($input, $parents, ''); $element[$key]['#value'] = ''; } } return $element; }
/** * {@inheritdoc} */ public static function setWidgetState(array $parents, $field_name, FormStateInterface $form_state, array $field_state) { NestedArray::setValue($form_state->getStorage(), static::getWidgetStateParents($parents, $field_name), $field_state); }
/** * Submit callback for remove buttons. */ public static function removeItemSubmit(&$form, FormStateInterface $form_state) { $triggering_element = $form_state->getTriggeringElement(); if (!empty($triggering_element['#attributes']['data-entity-id'])) { $id = $triggering_element['#attributes']['data-entity-id']; $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth); $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth); // Find and remove correct entity. $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id']))); $values = array_filter($values, function ($item) use($id) { return $item != $id; }); $values = implode(' ', $values); // Set new value for this widget. $target_id_element =& NestedArray::getValue($form, array_merge($array_parents, ['target_id'])); $form_state->setValueForElement($target_id_element, $values); NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $values); // Rebuild form. $form_state->setRebuild(); } }
/** * Handles validation errors for forms with limited validation. * * If validation errors are limited then remove any non validated form values, * so that only values that passed validation are left for submit callbacks. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param string $form_id * The unique string identifying the form. */ protected function handleErrorsWithLimitedValidation(&$form, FormStateInterface &$form_state, $form_id) { // If validation errors are limited then remove any non validated form values, // so that only values that passed validation are left for submit callbacks. $triggering_element = $form_state->getTriggeringElement(); if (isset($triggering_element['#limit_validation_errors']) && $triggering_element['#limit_validation_errors'] !== FALSE) { $values = array(); foreach ($triggering_element['#limit_validation_errors'] as $section) { // If the section exists within $form_state->getValues(), even if the // value is NULL, copy it to $values. $section_exists = NULL; $value = NestedArray::getValue($form_state->getValues(), $section, $section_exists); if ($section_exists) { NestedArray::setValue($values, $section, $value); } } // A button's #value does not require validation, so for convenience we // allow the value of the clicked button to be retained in its normal // $form_state->getValues() locations, even if these locations are not // included in #limit_validation_errors. if (!empty($triggering_element['#is_button'])) { $button_value = $triggering_element['#value']; // Like all input controls, the button value may be in the location // dictated by #parents. If it is, copy it to $values, but do not // override what may already be in $values. $parents = $triggering_element['#parents']; if (!NestedArray::keyExists($values, $parents) && NestedArray::getValue($form_state->getValues(), $parents) === $button_value) { NestedArray::setValue($values, $parents, $button_value); } // Additionally, self::doBuildForm() places the button value in // $form_state->getValue(BUTTON_NAME). If it's still there, after // validation handlers have run, copy it to $values, but do not override // what may already be in $values. $name = $triggering_element['#name']; if (!isset($values[$name]) && $form_state->getValue($name) === $button_value) { $values[$name] = $button_value; } } $form_state->setValues($values); } }
/** * Processes a machine-readable name form element. * * @param array $element * The form element to process. See main class documentation for properties. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The processed element. */ public static function processMachineName(&$element, FormStateInterface $form_state, &$complete_form) { // We need to pass the langcode to the client. $language = \Drupal::languageManager()->getCurrentLanguage(); // Apply default form element properties. $element += array('#title' => t('Machine-readable name'), '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), '#machine_name' => array(), '#field_prefix' => '', '#field_suffix' => '', '#suffix' => ''); // A form element that only wants to set one #machine_name property (usually // 'source' only) would leave all other properties undefined, if the defaults // were defined by an element plugin. Therefore, we apply the defaults here. $element['#machine_name'] += array('source' => array('label'), 'target' => '#' . $element['#id'], 'label' => t('Machine name'), 'replace_pattern' => '[^a-z0-9_]+', 'replace' => '_', 'standalone' => FALSE, 'field_prefix' => $element['#field_prefix'], 'field_suffix' => $element['#field_suffix']); // By default, machine names are restricted to Latin alphanumeric characters. // So, default to LTR directionality. if (!isset($element['#attributes'])) { $element['#attributes'] = array(); } $element['#attributes'] += array('dir' => LanguageInterface::DIRECTION_LTR); // The source element defaults to array('name'), but may have been overridden. if (empty($element['#machine_name']['source'])) { return $element; } // Retrieve the form element containing the human-readable name from the // complete form in $form_state. By reference, because we may need to append // a #field_suffix that will hold the live preview. $key_exists = NULL; $source = NestedArray::getValue($form_state->getCompleteForm(), $element['#machine_name']['source'], $key_exists); if (!$key_exists) { return $element; } $suffix_id = $source['#id'] . '-machine-name-suffix'; $element['#machine_name']['suffix'] = '#' . $suffix_id; if ($element['#machine_name']['standalone']) { $element['#suffix'] = $element['#suffix'] . ' <small id="' . $suffix_id . '"> </small>'; } else { // Append a field suffix to the source form element, which will contain // the live preview of the machine name. $source += array('#field_suffix' => ''); $source['#field_suffix'] = $source['#field_suffix'] . ' <small id="' . $suffix_id . '"> </small>'; $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); NestedArray::setValue($form_state->getCompleteForm(), $parents, $source['#field_suffix']); } $element['#attached']['library'][] = 'core/drupal.machine-name'; $element['#attached']['drupalSettings']['machineName']['#' . $source['#id']] = $element['#machine_name']; $element['#attached']['drupalSettings']['langcode'] = $language->getId(); return $element; }
/** * 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) { foreach ($element_values as $machine_name => $bundle_info) { $bundle_options[$machine_name] = $machine_name; } } } // New value parents. $parents = array_merge(array_slice($element['#parents'], 0, -1), array('target_bundles')); NestedArray::setValue($values, $parents, $bundle_options); }
/** * {@inheritdoc} */ public function set($property, $value) { NestedArray::setValue($this->storage, (array) $property, $value, TRUE); return $this; }
/** * Sets a value in this configuration object. * * @param string $key * Identifier to store value in configuration. * @param mixed $value * Value to associate with identifier. * * @return $this * The configuration object. * * @throws \Drupal\Core\Config\ConfigValueException * If $value is an array and any of its keys in any depth contains a dot. */ public function set($key, $value) { $value = $this->castSafeStrings($value); // The dot/period is a reserved character; it may appear between keys, but // not within keys. if (is_array($value)) { $this->validateKeys($value); } $parts = explode('.', $key); if (count($parts) == 1) { $this->data[$key] = $value; } else { NestedArray::setValue($this->data, $parts, $value); } return $this; }
/** * Performs validation of elements that are not subject to limited validation. * * @param array $elements * An associative array containing the structure of the form. * @param array $form_state * A keyed array containing the current state of the form. The current * user-submitted data is stored in $form_state['values'], though * form validation functions are passed an explicit copy of the * values for the sake of simplicity. Validation handlers can also * $form_state to pass information on to submit handlers. For example: * $form_state['data_for_submission'] = $data; * This technique is useful when validation requires file parsing, * web service requests, or other expensive requests that should * not be repeated in the submission step. */ protected function performRequiredValidation(&$elements, &$form_state) { // Verify that the value is not longer than #maxlength. if (isset($elements['#maxlength']) && Unicode::strlen($elements['#value']) > $elements['#maxlength']) { $this->setError($elements, $form_state, $this->t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => Unicode::strlen($elements['#value'])))); } if (isset($elements['#options']) && isset($elements['#value'])) { if ($elements['#type'] == 'select') { $options = OptGroup::flattenOptions($elements['#options']); } else { $options = $elements['#options']; } if (is_array($elements['#value'])) { $value = in_array($elements['#type'], array('checkboxes', 'tableselect')) ? array_keys($elements['#value']) : $elements['#value']; foreach ($value as $v) { if (!isset($options[$v])) { $this->setError($elements, $form_state, $this->t('An illegal choice has been detected. Please contact the site administrator.')); $this->watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); } } } elseif ($elements['#type'] == 'select' && !$elements['#multiple'] && $elements['#required'] && !isset($elements['#default_value']) && $elements['#value'] === $elements['#empty_value']) { $elements['#value'] = NULL; NestedArray::setValue($form_state['values'], $elements['#parents'], NULL, TRUE); } elseif (!isset($options[$elements['#value']])) { $this->setError($elements, $form_state, $this->t('An illegal choice has been detected. Please contact the site administrator.')); $this->watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); } } }
/** * 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']); } }
/** * {@inheritdoc} */ public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) { $field_name = $this->fieldDefinition->getName(); // Extract the values from $form_state->getValues(). $path = array_merge($form['#parents'], array($field_name)); $key_exists = NULL; // Convert the field value into expected array format. $values = $form_state->getValues(); $value = NestedArray::getValue($values, $path, $key_exists); if (empty($value)) { parent::extractFormValues($items, $form, $form_state); return; } if (!isset($value[0]['target_id'])) { NestedArray::setValue($values, $path, [['target_id' => reset($value)]]); $form_state->setValues($values); } parent::extractFormValues($items, $form, $form_state); }
/** * {@inheritdoc} */ public function setSetting($setting_name, $value) { NestedArray::setValue($this->settings, (array) $setting_name, $value); return $this; }
/** * Overrides the specified library asset. * * @param array $library * The containing library definition. * @param array $sub_key * An array containing the sub-keys specifying the library asset, e.g. * @code['js']@endcode or @code['css', 'component']@endcode * @param array $overrides * Specifies the overrides, this is an array where the key is the asset to * be overridden while the value is overriding asset. */ protected function setOverrideValue(array &$library, array $sub_key, array $overrides, $theme_path) { foreach ($overrides as $original => $replacement) { // Get the attributes of the asset to be overridden. If the key does // not exist, then throw an exception. $key_exists = NULL; $parents = array_merge($sub_key, [$original]); // Save the attributes of the library asset to be overridden. $attributes = NestedArray::getValue($library, $parents, $key_exists); if ($key_exists) { // Remove asset to be overridden. NestedArray::unsetValue($library, $parents); // No need to replace if FALSE is specified, since that is a removal. if ($replacement) { // Ensure the replacement path is relative to drupal root. $replacement = $this->resolveThemeAssetPath($theme_path, $replacement); $new_parents = array_merge($sub_key, [$replacement]); // Replace with an override if specified. NestedArray::setValue($library, $new_parents, $attributes); } } } }
/** * {@inheritdoc} */ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, array &$context) { $entity_links = array(); $view_mode = $context['view_mode']; if ($view_mode == 'search_index' || $view_mode == 'search_result' || $view_mode == 'print' || $view_mode == 'rss') { // Do not add any links if the entity is displayed for: // - search indexing. // - constructing a search result excerpt. // - print. // - rss. return array(); } $fields = $this->commentManager->getFields($entity->getEntityTypeId()); foreach ($fields as $field_name => $detail) { // Skip fields that the entity does not have. if (!$entity->hasField($field_name)) { continue; } $links = array(); $commenting_status = $entity->get($field_name)->status; if ($commenting_status != CommentItemInterface::HIDDEN) { // Entity has commenting status open or closed. $field_definition = $entity->getFieldDefinition($field_name); if ($view_mode == 'teaser') { // Teaser view: display the number of comments that have been posted, // or a link to add new comments if the user has permission, the // entity is open to new comments, and there currently are none. if ($this->currentUser->hasPermission('access comments')) { if (!empty($entity->get($field_name)->comment_count)) { $links['comment-comments'] = array('title' => $this->formatPlural($entity->get($field_name)->comment_count, '1 comment', '@count comments'), 'attributes' => array('title' => $this->t('Jump to the first comment.')), 'fragment' => 'comments', 'url' => $entity->urlInfo()); if ($this->moduleHandler->moduleExists('history')) { $links['comment-new-comments'] = array('title' => '', 'url' => Url::fromRoute('<current>'), 'attributes' => array('class' => 'hidden', 'title' => $this->t('Jump to the first new comment.'), 'data-history-node-last-comment-timestamp' => $entity->get($field_name)->last_comment_timestamp, 'data-history-node-field-name' => $field_name)); } } } // Provide a link to new comment form. if ($commenting_status == CommentItemInterface::OPEN) { $comment_form_location = $field_definition->getSetting('form_location'); if ($this->currentUser->hasPermission('post comments')) { $links['comment-add'] = array('title' => $this->t('Add new comments'), 'language' => $entity->language(), 'attributes' => array('title' => $this->t('Share your thoughts and opinions.')), 'fragment' => 'comment-form'); if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { $links['comment-add']['url'] = Url::fromRoute('comment.reply', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name]); } else { $links['comment-add'] += ['url' => $entity->urlInfo()]; } } elseif ($this->currentUser->isAnonymous()) { $links['comment-forbidden'] = array('title' => $this->commentManager->forbiddenMessage($entity, $field_name)); } } } else { // Entity in other view modes: add a "post comment" link if the user // is allowed to post comments and if this entity is allowing new // comments. if ($commenting_status == CommentItemInterface::OPEN) { $comment_form_location = $field_definition->getSetting('form_location'); if ($this->currentUser->hasPermission('post comments')) { // Show the "post comment" link if the form is on another page, or // if there are existing comments that the link will skip past. if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE || !empty($entity->get($field_name)->comment_count) && $this->currentUser->hasPermission('access comments')) { $links['comment-add'] = array('title' => $this->t('Add new comment'), 'attributes' => array('title' => $this->t('Share your thoughts and opinions.')), 'fragment' => 'comment-form'); if ($comment_form_location == CommentItemInterface::FORM_SEPARATE_PAGE) { $links['comment-add']['url'] = Url::fromRoute('comment.reply', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity->id(), 'field_name' => $field_name]); } else { $links['comment-add']['url'] = $entity->urlInfo(); } } } elseif ($this->currentUser->isAnonymous()) { $links['comment-forbidden'] = array('title' => $this->commentManager->forbiddenMessage($entity, $field_name)); } } } } if (!empty($links)) { $entity_links['comment__' . $field_name] = array('#theme' => 'links__entity__comment__' . $field_name, '#links' => $links, '#attributes' => array('class' => array('links', 'inline'))); if ($view_mode == 'teaser' && $this->moduleHandler->moduleExists('history') && $this->currentUser->isAuthenticated()) { $entity_links['comment__' . $field_name]['#cache']['contexts'][] = 'user'; $entity_links['comment__' . $field_name]['#attached']['library'][] = 'comment/drupal.node-new-comments-link'; // Embed the metadata for the "X new comments" link (if any) on this // entity. $entity_links['comment__' . $field_name]['#attached']['drupalSettings']['history']['lastReadTimestamps'][$entity->id()] = (int) history_read($entity->id()); $new_comments = $this->commentManager->getCountNewComments($entity); if ($new_comments > 0) { $page_number = $this->entityManager->getStorage('comment')->getNewCommentPageNumber($entity->{$field_name}->comment_count, $new_comments, $entity, $field_name); $query = $page_number ? ['page' => $page_number] : NULL; $value = ['new_comment_count' => (int) $new_comments, 'first_new_comment_link' => $entity->url('canonical', ['query' => $query, 'fragment' => 'new'])]; $parents = ['comment', 'newCommentsLinks', $entity->getEntityTypeId(), $field_name, $entity->id()]; NestedArray::setValue($entity_links['comment__' . $field_name]['#attached']['drupalSettings'], $parents, $value); } } } } return $entity_links; }
/** * {@inheritdoc} */ public static function setModalState(FormStateInterface $form_state, array $field_state) { NestedArray::setValue($form_state->getStorage(), ['search_results'], $field_state); }