From 83c52c3b1a39a10d3f0624ea640f7e211c2061a2 Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Fri, 27 Aug 2010 11:54:32 +0000 Subject: #763376 by fago, sun, noahb, effulgentsia, ksenzee, jhodgdon: Fixed Not validated form values appear in (). --- includes/common.inc | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ includes/form.inc | 53 ++++++++---------- 2 files changed, 174 insertions(+), 30 deletions(-) (limited to 'includes') diff --git a/includes/common.inc b/includes/common.inc index afb08eafd..3e9b9c7f9 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -5581,6 +5581,157 @@ function element_get_visible_children(array $elements) { return array_keys($visible_children); } +/** + * Sets a value in a nested array with variable depth. + * + * This helper function should be used when the depth of the array element you + * are changing may vary (that is, the number of parent keys is variable). It + * is primarily used for form structures and renderable arrays. + * + * Example: + * @code + * // Assume you have a 'signature' element somewhere in a form. It might be: + * $form['signature_settings']['signature'] = array( + * '#type' => 'text_format', + * '#title' => t('Signature'), + * ); + * // Or, it might be further nested: + * $form['signature_settings']['user']['signature'] = array( + * '#type' => 'text_format', + * '#title' => t('Signature'), + * ); + * @endcode + * + * To deal with the situation, the code needs to figure out the route to the + * element, given an array of parents that is either + * @code array('signature_settings', 'signature') @endcode in the first case or + * @code array('signature_settings', 'user', 'signature') @endcode in the second + * case. + * + * Without this helper function the only way to set the signature element in one + * line would be using eval(), which should be avoided: + * @code + * // Do not do this! Avoid eval(). + * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;'); + * @endcode + * + * Instead, use this helper function: + * @code + * drupal_array_set_nested_value($form, $parents, $element); + * @endcode + * + * However if the number of array parent keys is static, the value should always + * be set directly rather than calling this function. For instance, for the + * first example we could just do: + * @code + * $form['signature_settings']['signature'] = $element; + * @endcode + * + * @param $array + * A reference to the array to modify. + * @param $parents + * An array of parent keys, starting with the outermost key. + * @param $value + * The value to set. + * + * @see drupal_array_get_nested_value() + */ +function drupal_array_set_nested_value(&$array, $parents, $value) { + $ref = &$array; + foreach ($parents as $parent) { + // Note that PHP is fine with referencing a not existing array key - in this + // case it just creates an entry with NULL as value. + $ref = &$ref[$parent]; + } + $ref = $value; +} + +/** + * Retrieves a value from a nested array with variable depth. + * + * This helper function should be used when the depth of the array element you + * are changing may vary (that is, the number of parent keys is variable). It is + * primarily used for form structures and renderable arrays. + * + * Without this helper function the only way to get a nested array value with + * variable depth in one line would be using eval(), which should be avoided: + * @code + * // Do not do this! Avoid eval(). + * // May also throw a PHP notice, if the variable array keys do not exist. + * eval('$value = $array[\'' . implode("']['", $parents) . "'];"); + * @endcode + * + * Instead, use this helper function: + * @code + * list($value, $value_exists) = drupal_array_get_nested_value($form, $parents); + * if ($value_exists) { + * // ... do something with $value ... + * } + * @endcode + * + * However if the number of array parent keys is static, the value should always + * be get directly rather than calling this function. For instance: + * @code + * $value = $form['signature_settings']['signature']; + * @endcode + * + * @param $array + * The array from which to get the value. + * @param $parents + * An array of parent keys of the value, starting with the outermost key. + * + * @return + * An indexed array containing: + * - The requested nested value, if it exists, or NULL if it does not. + * - TRUE if all the parent keys exist, FALSE otherwise. + * + * @see drupal_array_set_nested_value() + * @see drupal_array_value_exists() + */ +function drupal_array_get_nested_value($array, $parents) { + foreach ($parents as $parent) { + if (isset($array[$parent])) { + $array = $array[$parent]; + } + else { + return array(NULL, FALSE); + } + } + return array($array, TRUE); +} + +/** + * Determines whether a value in a nested array with variable depth exists. + * + * This helper function should be used when the depth of the array element to be + * checked may vary (that is, the number of parent keys is variable). See + * drupal_array_set_nested_value() for details. This helper is primarily used + * for form structures and renderable arrays. + * + * @param $array + * The array with the value to check for. + * @param $parents + * An array of parent keys of the value, starting with the outermost key. + * + * @return + * TRUE if all the parent keys exist, FALSE otherwise. + * + * @see drupal_array_set_nested_value() + * @see drupal_array_get_nested_value() + */ +function drupal_array_nested_value_exists($array, $parents) { + foreach ($parents as $parent) { + if (isset($array[$parent])) { + $array = $array[$parent]; + } + else { + return FALSE; + } + } + return TRUE; +} + + /** * Provide theme registration for themes across .inc files. */ diff --git a/includes/form.inc b/includes/form.inc index 6501941f2..3aaeec99a 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -953,6 +953,24 @@ function drupal_validate_form($form_id, &$form, &$form_state) { _form_validate($form, $form_state, $form_id); $validated_forms[$form_id] = TRUE; + + // If validation errors are limited then remove any non validated form values, + // so that only values that passed validation are left for submit callbacks. + if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { + $values = array(); + foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { + list($value, $value_exists) = drupal_array_get_nested_value($form_state['values'], $section); + if ($value_exists) { + drupal_array_set_nested_value($values, $section, $value); + } + } + // For convenience we always make the value of the pressed button available. + if (isset($form_state['triggering_element']['#button_type'])) { + $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; + drupal_array_set_nested_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']); + } + $form_state['values'] = $values; + } } /** @@ -1727,11 +1745,9 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { // submit explicit NULL values when calling drupal_form_submit(), so we do // not modify $form_state['input'] for them. if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) { - // We leverage the internal logic of form_set_value() to change the - // input values by passing $form_state['input'] instead of the usual - // $form_state['values']. In effect, this adds the necessary parent keys - // to $form_state['input'] and sets the element's input value to NULL. - _form_set_value($form_state['input'], $element, $element['#parents'], NULL); + // Add the necessary parent keys to $form_state['input'] and sets the + // element's input value to NULL. + drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL); $input_exists = TRUE; } // If we have input for the current element, assign it to the #value @@ -1790,11 +1806,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { // Set the element's value in $form_state['values'], but only, if its key // does not exist yet (a #value_callback may have already populated it). - $values = $form_state['values']; - foreach ($element['#parents'] as $key) { - $values = (isset($values[$key]) ? $values[$key] : NULL); - } - if (!isset($values)) { + if (!drupal_array_nested_value_exists($form_state['values'], $element['#parents'])) { form_set_value($element, $element['#value'], $form_state); } } @@ -2140,26 +2152,7 @@ function form_type_token_value($element, $input = FALSE) { * Form state array where the value change should be recorded. */ function form_set_value($element, $value, &$form_state) { - _form_set_value($form_state['values'], $element, $element['#parents'], $value); -} - -/** - * Helper function for form_set_value() and _form_builder_handle_input_element(). - * - * We iterate over $parents and create nested arrays for them in $form_values if - * needed. Then we insert the value into the last parent key. - */ -function _form_set_value(&$form_values, $element, $parents, $value) { - $parent = array_shift($parents); - if (empty($parents)) { - $form_values[$parent] = $value; - } - else { - if (!isset($form_values[$parent])) { - $form_values[$parent] = array(); - } - _form_set_value($form_values[$parent], $element, $parents, $value); - } + drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value); } /** -- cgit v1.2.3