diff --git includes/common.inc includes/common.inc index 1066bdf..653f83b 100644 --- includes/common.inc +++ includes/common.inc @@ -5491,6 +5491,54 @@ function element_get_visible_children(array $elements) { } /** + * Sets a value in a deeply nested array. + * + * @param $array + * A reference to the array to modify. + * @param $parents + * An ordered array of parent keys, starting with the outer most entry. + * @param $value + * The value to set. + */ +function drupal_set_array_deep(&$array, $parents, $value) { + $parent = array_shift($parents); + if (empty($parents)) { + $array[$parent] = $value; + } + else { + if (!isset($array[$parent])) { + $array[$parent] = array(); + } + drupal_set_array_deep($array[$parent], $parents, $value); + } +} + +/** + * Gets a value sitting in a deeply nested array. + * + * @param $array + * The array from which to get a value. + * @param $parents + * An ordered array of parent keys of the value, starting with the outer most + * entry. + * @return + * An array with the first entry being the value. As second entry a boolean + * flag is returned, which determines whether all given parent keys are set. + */ +function drupal_get_array_deep($array, $parents) { + $parent = array_shift($parents); + if (array_key_exists($parent, $array)) { + if (empty($parents)) { + return array($array[$parent], TRUE); + } + if (is_array($array[$parent])) { + return drupal_get_array_deep($array[$parent], $parents); + } + } + return array(NULL, FALSE); +} + +/** * Provide theme registration for themes across .inc files. */ function drupal_common_theme() { diff --git includes/form.inc includes/form.inc index 4d5633c..1bfbf50 100644 --- includes/form.inc +++ includes/form.inc @@ -917,6 +917,25 @@ 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 remove any not 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(); + // For convenience we always make the form values of pressed buttons + // available. + if (isset($form_state['triggering_element']['#button_type'])) { + $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; + $form_state['triggering_element']['#limit_validation_errors'][] = $form_state['triggering_element']['#parents']; + } + foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { + list($value, $value_exists) = drupal_get_array_deep($form_state['values'], $section); + if ($value_exists) { + drupal_set_array_deep($values, $section, $value); + } + } + $form_state['values'] = $values; + } } /** @@ -1683,11 +1702,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_set_array_deep($form_state['input'], $element['#parents'], NULL); $input_exists = TRUE; } // If we have input for the current element, assign it to the #value @@ -1746,11 +1763,8 @@ 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)) { + list($values, $value_exists) = drupal_get_array_deep($form_state['values'], $element['#parents']); + if (!$value_exists) { form_set_value($element, $element['#value'], $form_state); } } @@ -2082,26 +2096,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_set_array_deep($form_state['values'], $element['#parents'], $value); } /** diff --git modules/file/file.module modules/file/file.module index 4075d65..2d75000 100644 --- modules/file/file.module +++ modules/file/file.module @@ -553,10 +553,7 @@ function file_managed_file_submit($form, &$form_state) { // and set $element to the managed_file element that contains that button. $parents = $form_state['triggering_element']['#array_parents']; $button_key = array_pop($parents); - $element = $form; - foreach ($parents as $parent) { - $element = $element[$parent]; - } + list($element) = drupal_get_array_deep($form, $parents); // No action is needed here for the upload button, because all file uploads on // the form are processed by file_managed_file_value() regardless of which @@ -574,13 +571,10 @@ function file_managed_file_submit($form, &$form_state) { // run, and for form building functions that run during the rebuild, such as // when the managed_file element is part of a field widget. // $form_state['input'] must be updated so that file_managed_file_value() - // has correct information during the rebuild. The Form API provides no - // equivalent of form_set_value() for updating $form_state['input'], so - // inline that implementation with the same logic that form_set_value() - // uses. + // has correct information during the rebuild. $values_element = $element['#extended'] ? $element['fid'] : $element; form_set_value($values_element, NULL, $form_state); - _form_set_value($form_state['input'], $values_element, $values_element['#parents'], NULL); + drupal_set_array_deep($form_state['input'], $values_element['#parents'], NULL); } // Set the form to rebuild so that $form is correctly updated in response to diff --git modules/simpletest/tests/form.test modules/simpletest/tests/form.test index efaac4b..0e094e4 100644 --- modules/simpletest/tests/form.test +++ modules/simpletest/tests/form.test @@ -324,6 +324,10 @@ class FormValidationTestCase extends DrupalWebTestCase { $this->assertNoText(t('!name field is required.', array('!name' => 'Title'))); $this->assertText('Test element is invalid'); + // Ensure not validated values are not available to submit handlers. + $this->drupalPost($path, array('test' => 'valid'), t('Partial validate')); + $this->assertText('Only validated values appear in the form values.'); + // Now test full form validation and ensure that the #element_validate // handler is still triggered. $this->drupalPost($path, $edit, t('Full validate')); diff --git modules/simpletest/tests/form_test.module modules/simpletest/tests/form_test.module index 065df57..d496655 100644 --- modules/simpletest/tests/form_test.module +++ modules/simpletest/tests/form_test.module @@ -296,7 +296,7 @@ function form_test_limit_validation_errors_form($form, &$form_state) { $form['actions']['partial'] = array( '#type' => 'submit', '#limit_validation_errors' => array(array('test')), - '#submit' => array(), + '#submit' => array('form_test_limit_validation_errors_form_partial_submit'), '#value' => t('Partial validate'), ); $form['actions']['full'] = array( @@ -316,6 +316,15 @@ function form_test_limit_validation_errors_element_validate_test(&$element, &$fo } /** + * Form submit handler for the partial validation submit button. + */ +function form_test_limit_validation_errors_form_partial_submit($form, $form_state) { + if (!isset($form_state['values']['title']) && isset($form_state['values']['test'])) { + drupal_set_message('Only validated values appear in the form values.'); + } +} + +/** * Create a header and options array. Helper function for callbacks. */ function _form_test_tableselect_get_data() {