Commit 83c52c3b authored by webchick's avatar webchick

#763376 by fago, sun, noahb, effulgentsia, ksenzee, jhodgdon: Fixed Not...

#763376 by fago, sun, noahb, effulgentsia, ksenzee, jhodgdon: Fixed Not validated form values appear in ().
parent d7d9587d
......@@ -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.
*/
......
......@@ -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);
}
/**
......
......@@ -572,10 +572,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_array_get_nested_value($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
......@@ -593,13 +590,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_array_set_nested_value($form_state['input'], $values_element['#parents'], NULL);
}
// Set the form to rebuild so that $form is correctly updated in response to
......
......@@ -42,7 +42,10 @@ class FormsTestCase extends DrupalWebTestCase {
$elements['password']['empty_values'] = $empty_strings;
$elements['password_confirm']['element'] = array('#title' => $this->randomName(), '#type' => 'password_confirm');
$elements['password_confirm']['empty_values'] = $empty_strings;
// Provide empty values for both password fields.
foreach ($empty_strings as $key => $value) {
$elements['password_confirm']['empty_values'][$key] = array('pass1' => $value, 'pass2' => $value);
}
$elements['textarea']['element'] = array('#title' => $this->randomName(), '#type' => 'textarea');
$elements['textarea']['empty_values'] = $empty_strings;
......@@ -77,8 +80,7 @@ class FormsTestCase extends DrupalWebTestCase {
$element = $data['element']['#title'];
$form[$element] = $data['element'];
$form[$element]['#required'] = $required;
$form_state['values'][$element] = $empty;
$form_state['input'] = $form_state['values'];
$form_state['input'][$element] = $empty;
$form_state['input']['form_id'] = $form_id;
$form_state['method'] = 'post';
drupal_prepare_form($form_id, $form, $form_state);
......@@ -405,6 +407,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('title' => '', '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'));
......@@ -616,11 +622,11 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
);
// Test with a valid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'row1'));
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('row1' => 'row1')));
$this->assertFalse(isset($errors['tableselect']), t('Option checker allows valid values for checkboxes.'));
// Test with an invalid value.
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => 'non_existing_value'));
list($processed_form, $form_state, $errors) = $this->formSubmitHelper($form, array('tableselect' => array('non_existing_value' => 'non_existing_value')));
$this->assertTrue(isset($errors['tableselect']), t('Option checker disallows invalid values for checkboxes.'));
}
......
......@@ -313,7 +313,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(
......@@ -332,6 +332,17 @@ 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) {
// The title has not been validated, thus its value - in case of the test case
// an empty string - may not be set.
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.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment