Commit 33f0b083 authored by Dries's avatar Dries

- Patch #426056 by effulgentsia, Berdir, sun, boombatower, tstoeckler: fixed...

- Patch #426056 by effulgentsia, Berdir, sun, boombatower, tstoeckler: fixed server-side enforcement of #disabled is inconsistent.
parent 98a3f226
......@@ -983,7 +983,7 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) {
// A simple call to empty() will not cut it here as some fields, like
// checkboxes, can return a valid value of '0'. Instead, check the
// length if it's a string, and the item count if it's an array.
// An unchecked checkbox has a #value of numeric 0, different than string
// An unchecked checkbox has a #value of integer 0, different than string
// '0', which could be a valid value.
if (isset($elements['#needs_validation']) && $elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === 0)) {
form_error($elements, $t('!name field is required.', array('!name' => $elements['#title'])));
......@@ -1405,15 +1405,45 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) {
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 sumitted, 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'])) {
$element['#attributes']['disabled'] = 'disabled';
if (!empty($element['#allow_focus'])) {
$element['#attributes']['readonly'] = 'readonly';
}
else {
$element['#attributes']['disabled'] = 'disabled';
}
}
// Set the element's #value property.
if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
$value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
if ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))) {
// 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
// drupal_form_submit() 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 drupal_form_submit() may bypass access restriction and be
// treated as high-privelege users instead.
if (empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])))) {
// Get the input for the current element. NULL values in the input need to
// be explicitly distinguished from missing input. (see below)
$input = $form_state['input'];
......@@ -1698,18 +1728,11 @@ function form_type_image_button_value($form, $input, $form_state) {
*/
function form_type_checkbox_value($element, $input = FALSE) {
if ($input !== FALSE) {
if (empty($element['#disabled'])) {
// Successful (checked) checkboxes are present with a value (possibly '0').
// http://www.w3.org/TR/html401/interact/forms.html#successful-controls
// For an unchecked checkbox, we return numeric 0, so we can explicitly
// test for a value different than string '0'.
return isset($input) ? $element['#return_value'] : 0;
}
else {
// Disabled form controls are not submitted by the browser. Ignore any
// submitted value and always return default.
return $element['#default_value'];
}
// Successful (checked) checkboxes are present with a value (possibly '0').
// http://www.w3.org/TR/html401/interact/forms.html#successful-controls
// For an unchecked checkbox, we return integer 0, so we can explicitly
// test for a value different than string '0'.
return isset($input) ? $element['#return_value'] : 0;
}
}
......@@ -2294,9 +2317,12 @@ function form_process_radios($element) {
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
'#disabled' => isset($element['#disabled']) ? $element['#disabled'] : NULL,
);
foreach (array('#ajax', '#disabled', '#allow_focus') as $property) {
if (isset($element[$property])) {
$element[$key][$property] = $element[$property];
}
}
}
}
}
......@@ -2326,7 +2352,7 @@ function theme_checkbox($variables) {
$checkbox .= 'name="' . $element['#name'] . '" ';
$checkbox .= 'id="' . $element['#id'] . '" ' ;
$checkbox .= 'value="' . $element['#return_value'] . '" ';
// Unchecked checkbox has #value of numeric 0.
// Unchecked checkbox has #value of integer 0.
if ($element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
$checkbox .= 'checked="checked" ';
}
......@@ -2398,9 +2424,12 @@ function form_process_checkboxes($element) {
'#return_value' => $key,
'#default_value' => isset($value[$key]) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
'#disabled' => isset($element['#disabled']) ? $element['#disabled'] : NULL,
);
foreach (array('#ajax', '#disabled', '#allow_focus') as $property) {
if (isset($element[$property])) {
$element[$key][$property] = $element[$property];
}
}
}
}
}
......@@ -2550,8 +2579,6 @@ function form_process_tableselect($element) {
'#return_value' => $key,
'#default_value' => isset($value[$key]) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
'#disabled' => isset($element['#disabled']) ? $element['#disabled'] : NULL,
);
}
else {
......@@ -2566,10 +2593,13 @@ function form_process_tableselect($element) {
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
'#disabled' => isset($element['#disabled']) ? $element['#disabled'] : NULL,
);
}
foreach (array('#ajax', '#disabled', '#allow_focus') as $property) {
if (isset($element[$property])) {
$element[$key][$property] = $element[$property];
}
}
}
}
}
......
......@@ -156,23 +156,54 @@ class FormsTestCase extends DrupalWebTestCase {
* @see _form_test_disabled_elements()
*/
function testDisabledElements() {
// Submit the form, and fetch the default values.
$this->drupalPost('form-test/disabled-elements', array(), t('Submit'));
$returned_values = drupal_json_decode($this->content);
// Get the default value from the form.
// Get the raw form in its original state.
$form_state = array();
$form = _form_test_disabled_elements(array(), $form_state);
// Build a submission that tries to hijack the form by submitting input for
// elements that are disabled.
$edit = array();
foreach (element_children($form) as $key) {
if (isset($form[$key]['#default_value'])) {
$expected_value = $form[$key]['#default_value'];
if (isset($form[$key]['#test_hijack_value'])) {
if (is_array($form[$key]['#test_hijack_value'])) {
foreach ($form[$key]['#test_hijack_value'] as $subkey => $value) {
$edit[$key . '[' . $subkey . ']'] = $value;
}
}
else {
$edit[$key] = $form[$key]['#test_hijack_value'];
}
}
}
if ($key == 'checkboxes_multiple') {
// Checkboxes values are not filtered out.
$returned_values[$key] = array_filter($returned_values[$key]);
// Submit the form with no input, as the browser does for disabled elements,
// and fetch the $form_state['values'] that is passed to the submit handler.
$this->drupalPost('form-test/disabled-elements', array(), t('Submit'));
$returned_values['normal'] = drupal_json_decode($this->content);
// Do the same with input, as could happen if JavaScript un-disables an
// element. drupalPost() emulates a browser by not submitting input for
// disabled elements, so we need to un-disable those elements first.
$this->drupalGet('form-test/disabled-elements');
foreach ($this->xpath('//*[@disabled]') as $element) {
unset($element['disabled']);
}
$this->drupalPost(NULL, $edit, t('Submit'));
$returned_values['hijacked'] = drupal_json_decode($this->content);
// Ensure that the returned values match the form's default values in both
// cases.
foreach ($returned_values as $type => $values) {
foreach (element_children($form) as $key) {
if (isset($form[$key]['#default_value'])) {
$expected_value = $form[$key]['#default_value'];
if ($key == 'checkboxes_multiple') {
// Checkboxes values are not filtered out.
$values[$key] = array_filter($values[$key]);
}
$this->assertIdentical($expected_value, $values[$key], t('Default value for %type: expected %expected, returned %returned.', array('%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($values[$key], TRUE))));
}
$this->assertIdentical($expected_value, $returned_values[$key], t('Default value for %type: expected %expected, returned %returned.', array('%type' => $key, '%expected' => var_export($expected_value, TRUE), '%returned' => var_export($returned_values[$key], TRUE))));
}
}
}
......
......@@ -706,6 +706,7 @@ function _form_test_disabled_elements($form, &$form_state) {
'#type' => $type,
'#title' => $type,
'#default_value' => $type,
'#test_hijack_value' => 'HIJACK',
'#disabled' => TRUE,
);
}
......@@ -721,6 +722,9 @@ function _form_test_disabled_elements($form, &$form_state) {
),
'#multiple' => TRUE,
'#default_value' => array('test_2' => 'test_2'),
// The keys of #test_hijack_value need to match the #name of the control.
// @see FormsTestCase::testDisabledElements()
'#test_hijack_value' => $type == 'select' ? array('' => 'test_1') : array('test_1' => 'test_1'),
'#disabled' => TRUE,
);
}
......@@ -736,6 +740,7 @@ function _form_test_disabled_elements($form, &$form_state) {
),
'#multiple' => FALSE,
'#default_value' => 'test_2',
'#test_hijack_value' => 'test_1',
'#disabled' => TRUE,
);
}
......@@ -747,6 +752,7 @@ function _form_test_disabled_elements($form, &$form_state) {
'#title' => $type . ' (unchecked)',
'#return_value' => 1,
'#default_value' => 0,
'#test_hijack_value' => 1,
'#disabled' => TRUE,
);
$form[$type . '_checked'] = array(
......@@ -754,6 +760,7 @@ function _form_test_disabled_elements($form, &$form_state) {
'#title' => $type . ' (checked)',
'#return_value' => 1,
'#default_value' => 1,
'#test_hijack_value' => NULL,
'#disabled' => TRUE,
);
}
......@@ -763,6 +770,7 @@ function _form_test_disabled_elements($form, &$form_state) {
'#type' => 'weight',
'#title' => 'weight',
'#default_value' => 10,
'#test_hijack_value' => 5,
'#disabled' => TRUE,
);
......@@ -776,6 +784,11 @@ function _form_test_disabled_elements($form, &$form_state) {
'month' => 11,
'year' => 1978,
),
'#test_hijack_value' => array(
'day' => 20,
'month' => 12,
'year' => 1979,
),
);
$form['submit'] = array(
......
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