#763376 by fago, sun, noahb, effulgentsia, ksenzee, jhodgdon: Fixed Not validated form values appear in ().
parent
d7d9587d6a
commit
83c52c3b1a
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue