From 699afdd1be4b8034c36474c7191972604bf5f54e Mon Sep 17 00:00:00 2001 From: Dries Buytaert <dries@buytaert.net> Date: Sat, 31 Jul 2010 04:01:51 +0000 Subject: [PATCH] - Patch #690980 by seutje, sun, seanyo, aaroncouch: disabled form elements not properly rendered. --- includes/form.inc | 171 +++++++++++++++------- modules/profile/profile.test | 2 +- modules/simpletest/tests/form.test | 68 ++++++++- modules/simpletest/tests/form_test.module | 58 ++++++++ themes/seven/style.css | 14 ++ 5 files changed, 256 insertions(+), 57 deletions(-) diff --git a/includes/form.inc b/includes/form.inc index 28b94b65ea3f..c5525c9e2ab3 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -2368,15 +2368,16 @@ function theme_fieldset($variables) { */ function theme_radio($variables) { $element = $variables['element']; + $element['#attributes']['type'] = 'radio'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#return_value']; + if (check_plain($element['#value']) == $element['#return_value']) { + $element['#attributes']['checked'] = 'checked'; + } _form_set_class($element, array('form-radio')); - $output = '<input type="radio" '; - $output .= 'id="' . $element['#id'] . '" '; - $output .= 'name="' . $element['#name'] . '" '; - $output .= 'value="' . $element['#return_value'] . '" '; - $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' '; - $output .= drupal_attributes($element['#attributes']) . ' />'; - return $output; + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** @@ -2602,19 +2603,17 @@ function form_process_radios($element) { function theme_checkbox($variables) { $element = $variables['element']; $t = get_t(); - _form_set_class($element, array('form-checkbox')); - $checkbox = '<input '; - $checkbox .= 'type="checkbox" '; - $checkbox .= 'name="' . $element['#name'] . '" '; - $checkbox .= 'id="' . $element['#id'] . '" ' ; - $checkbox .= 'value="' . $element['#return_value'] . '" '; + $element['#attributes']['type'] = 'checkbox'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#return_value']; // Unchecked checkbox has #value of integer 0. if ($element['#value'] !== 0 && $element['#value'] == $element['#return_value']) { - $checkbox .= 'checked="checked" '; + $element['#attributes']['checked'] = 'checked'; } - $checkbox .= drupal_attributes($element['#attributes']) . ' />'; + _form_set_class($element, array('form-checkbox')); - return $checkbox; + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** @@ -3057,9 +3056,18 @@ function theme_submit($variables) { */ function theme_button($variables) { $element = $variables['element']; + $element['#attributes']['type'] = 'submit'; + if (!empty($element['#name'])) { + $element['#attributes']['name'] = $element['#name']; + } + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#value']; $element['#attributes']['class'][] = 'form-' . $element['#button_type']; + if (!empty($element['#attributes']['disabled'])) { + $element['#attributes']['class'][] = 'form-button-disabled'; + } - return '<input type="submit" ' . (empty($element['#name']) ? '' : 'name="' . $element['#name'] . '" ') . 'id="' . $element['#id'] . '" value="' . check_plain($element['#value']) . '" ' . drupal_attributes($element['#attributes']) . " />\n"; + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** @@ -3074,15 +3082,24 @@ function theme_button($variables) { */ function theme_image_button($variables) { $element = $variables['element']; + $element['#attributes']['type'] = 'submit'; + $element['#attributes']['name'] = $element['#name']; + if (!empty($element['#value'])) { + $element['#attributes']['value'] = $element['#value']; + } + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['src'] = file_create_url($element['#src']); + if (!empty($element['#title'])) { + $element['#attributes']['alt'] = $element['#title']; + $element['#attributes']['title'] = $element['#title']; + } + $element['#attributes']['class'][] = 'form-' . $element['#button_type']; + if (!empty($element['#attributes']['disabled'])) { + $element['#attributes']['class'][] = 'form-button-disabled'; + } - return '<input type="image" name="' . $element['#name'] . '" ' . - (!empty($element['#value']) ? ('value="' . check_plain($element['#value']) . '" ') : '') . - 'id="' . $element['#id'] . '" ' . - drupal_attributes($element['#attributes']) . - ' src="' . file_create_url($element['#src']) . '" ' . - (!empty($element['#title']) ? 'alt="' . check_plain($element['#title']) . '" title="' . check_plain($element['#title']) . '" ' : '' ) . - "/>\n"; + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** @@ -3097,7 +3114,11 @@ function theme_image_button($variables) { */ function theme_hidden($variables) { $element = $variables['element']; - return '<input type="hidden" name="' . $element['#name'] . '" id="' . $element['#id'] . '" value="' . check_plain($element['#value']) . "\" " . drupal_attributes($element['#attributes']) . " />\n"; + $element['#attributes']['type'] = 'hidden'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#value']; + return '<input' . drupal_attributes($element['#attributes']) . " />\n"; } /** @@ -3113,20 +3134,33 @@ function theme_hidden($variables) { */ function theme_textfield($variables) { $element = $variables['element']; - $size = empty($element['#size']) ? '' : ' size="' . $element['#size'] . '"'; - $maxlength = empty($element['#maxlength']) ? '' : ' maxlength="' . $element['#maxlength'] . '"'; - $class = array('form-text'); - $extra = ''; - $output = ''; + $element['#attributes']['type'] = 'text'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#value']; + if (!empty($element['#size'])) { + $element['#attributes']['size'] = $element['#size']; + } + if (!empty($element['#maxlength'])) { + $element['#attributes']['maxlength'] = $element['#maxlength']; + } + _form_set_class($element, array('form-text')); + $extra = ''; if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) { drupal_add_js('misc/autocomplete.js'); - $class[] = 'form-autocomplete'; - $extra = '<input class="autocomplete" type="hidden" id="' . $element['#id'] . '-autocomplete" value="' . check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) . '" disabled="disabled" />'; + $element['#attributes']['class'][] = 'form-autocomplete'; + + $attributes = array(); + $attributes['type'] = 'hidden'; + $attributes['id'] = $element['#id'] . '-autocomplete'; + $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE)); + $attributes['disabled'] = 'disabled'; + $attributes['class'][] = 'autocomplete'; + $extra = '<input' . drupal_attributes($attributes) . ' />'; } - _form_set_class($element, $class); - $output .= '<input type="text"' . $maxlength . ' name="' . $element['#name'] . '" id="' . $element['#id'] . '"' . $size . ' value="' . check_plain($element['#value']) . '"' . drupal_attributes($element['#attributes']) . ' />'; + $output = '<input' . drupal_attributes($element['#attributes']) . ' />'; return $output . $extra; } @@ -3143,14 +3177,16 @@ function theme_textfield($variables) { */ function theme_form($variables) { $element = $variables['element']; - // Anonymous div to satisfy XHTML compliance. - $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : ''; - + if (!empty($element['#action'])) { + $element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']); + } + $element['#attributes']['method'] = $element['#method']; if (empty($element['#attributes']['accept-charset'])) { $element['#attributes']['accept-charset'] = "UTF-8"; } - - return '<form '. $action .' method="'. $element['#method'] .'" id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n"; + $element['#attributes']['id'] = $element['#id']; + // Anonymous DIV to satisfy XHTML compliance. + return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>'; } /** @@ -3166,10 +3202,16 @@ function theme_form($variables) { */ function theme_textarea($variables) { $element = $variables['element']; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#value']; + $element['#attributes']['cols'] = $element['#cols']; + $element['#attributes']['rows'] = $element['#rows']; + _form_set_class($element, array('form-textarea')); + $wrapper_attributes = array( 'class' => array('form-textarea-wrapper'), ); - $class = array('form-textarea'); // Add resizable behavior. if (!empty($element['#resizable'])) { @@ -3178,10 +3220,7 @@ function theme_textarea($variables) { } $output = '<div' . drupal_attributes($wrapper_attributes) . '>'; - - _form_set_class($element, $class); - $output .= '<textarea cols="' . $element['#cols'] . '" rows="' . $element['#rows'] . '" name="' . $element['#name'] . '" id="' . $element['#id'] . '" ' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>'; - + $output .= '<textarea' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>'; $output .= '</div>'; return $output; } @@ -3199,12 +3238,19 @@ function theme_textarea($variables) { */ function theme_password($variables) { $element = $variables['element']; - $size = $element['#size'] ? ' size="' . $element['#size'] . '" ' : ''; - $maxlength = $element['#maxlength'] ? ' maxlength="' . $element['#maxlength'] . '" ' : ''; - + $element['#attributes']['type'] = 'password'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + $element['#attributes']['value'] = $element['#value']; + if (!empty($element['#size'])) { + $element['#attributes']['size'] = $element['#size']; + } + if (!empty($element['#maxlength'])) { + $element['#attributes']['maxlength'] = $element['#maxlength']; + } _form_set_class($element, array('form-text')); - $output = '<input type="password" name="' . $element['#name'] . '" id="' . $element['#id'] . '" ' . $maxlength . $size . drupal_attributes($element['#attributes']) . ' />'; - return $output; + + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** @@ -3237,17 +3283,30 @@ function form_process_weight($element) { */ function theme_file($variables) { $element = $variables['element']; + $element['#attributes']['type'] = 'file'; + $element['#attributes']['name'] = $element['#name']; + $element['#attributes']['id'] = $element['#id']; + if (!empty($element['#size'])) { + $element['#attributes']['size'] = $element['#size']; + } _form_set_class($element, array('form-file')); - return '<input type="file" name="' . $element['#name'] . '"' . ($element['#attributes'] ? ' ' . drupal_attributes($element['#attributes']) : '') . ' id="' . $element['#id'] . '" size="' . $element['#size'] . "\" />\n"; + + return '<input' . drupal_attributes($element['#attributes']) . ' />'; } /** * Returns HTML for a form element. * - * Each form element is wrapped in a DIV with #type and #name classes. In - * addition to the element itself, the div contains a label before or after - * the element based on the optional #title_display property. After the label - * and fields this function outputs the optional element #description. + * Each form element is wrapped in a DIV container having the following CSS + * classes: + * - form-item: Generic for all form elements. + * - form-type-#type: The internal element #type. + * - form-item-#name: The internal form element #name (usually derived from the + * $form structure and set via form_builder()). + * - form-disabled: Only set if the form element is #disabled. + * + * In addition to the element itself, the DIV contains a label for the element + * based on the optional #title_display property, and an optional #description. * * The optional #title_display property can have these values: * - before: The label is output before the element. This is the default. @@ -3298,6 +3357,10 @@ function theme_form_element($variables) { if (!empty($element['#name'])) { $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); } + // Add a class for disabled elements to facilitate cross-browser styling. + if (!empty($element['#attributes']['disabled'])) { + $attributes['class'][] = 'form-disabled'; + } $output = '<div' . drupal_attributes($attributes) . '>' . "\n"; // If #title is not set, we don't display any label or required marker. diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 7216a522ff43..27b32d3dbb58 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -335,7 +335,7 @@ class ProfileTestAutocomplete extends ProfileTestCase { $this->setProfileField($field, $field['value']); // Set some html for what we want to see in the page output later. - $autocomplete_html = '<input class="autocomplete" type="hidden" id="' . drupal_html_id('edit-' . $field['form_name'] . '-autocomplete') . '" value="' . url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)) . '" disabled="disabled" />'; + $autocomplete_html = '<input type="hidden" id="' . drupal_html_id('edit-' . $field['form_name'] . '-autocomplete') . '" value="' . url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)) . '" disabled="disabled" class="autocomplete" />'; $field_html = '<input type="text" maxlength="255" name="' . $field['form_name'] . '" id="' . drupal_html_id('edit-' . $field['form_name']) . '" size="60" value="' . $field['value'] . '" class="form-text form-autocomplete required" />'; // Check that autocompletion html is found on the user's profile edit page. diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index 2e1564a6ea36..525a8a2910d5 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -193,7 +193,7 @@ class FormsTestCase extends DrupalWebTestCase { // All the elements should be marked as disabled, including the ones below // the disabled container. - $this->assertEqual(count($disabled_elements), 20, t('The correct elements have the disabled property in the HTML code.')); + $this->assertEqual(count($disabled_elements), 32, t('The correct elements have the disabled property in the HTML code.')); $this->drupalPost(NULL, $edit, t('Submit')); $returned_values['hijacked'] = drupal_json_decode($this->content); @@ -211,7 +211,12 @@ class FormsTestCase extends DrupalWebTestCase { function assertFormValuesDefault($values, $form) { foreach (element_children($form) as $key) { if (isset($form[$key]['#default_value'])) { - $expected_value = $form[$key]['#default_value']; + if (isset($form[$key]['#expected_value'])) { + $expected_value = $form[$key]['#expected_value']; + } + else { + $expected_value = $form[$key]['#default_value']; + } if ($key == 'checkboxes_multiple') { // Checkboxes values are not filtered out. @@ -225,6 +230,65 @@ class FormsTestCase extends DrupalWebTestCase { } } + /** + * Verify markup for disabled form elements. + * + * @see _form_test_disabled_elements() + */ + function testDisabledMarkup() { + $this->drupalGet('form-test/disabled-elements'); + $form_state = array(); + $form = _form_test_disabled_elements(array(), $form_state); + $type_map = array( + 'textarea' => 'textarea', + 'select' => 'select', + 'weight' => 'select', + 'date' => 'select', + ); + + foreach ($form as $name => $item) { + // Skip special #types. + if (!isset($item['#type']) || in_array($item['#type'], array('hidden', 'text_format'))) { + continue; + } + // Setup XPath and CSS class depending on #type. + if (in_array($item['#type'], array('image_button', 'button', 'submit'))) { + $path = "//!type[contains(@class, :div-class) and @value=:value]"; + $class = 'form-button-disabled'; + } + else { + // starts-with() required for checkboxes. + $path = "//div[contains(@class, :div-class)]/descendant::!type[starts-with(@name, :name)]"; + $class = 'form-disabled'; + } + // Replace DOM element name in $path according to #type. + $type = 'input'; + if (isset($type_map[$item['#type']])) { + $type = $type_map[$item['#type']]; + } + $path = strtr($path, array('!type' => $type)); + // Verify that the element exists. + $element = $this->xpath($path, array( + ':name' => check_plain($name), + ':div-class' => $class, + ':value' => isset($item['#value']) ? $item['#value'] : '', + )); + $this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => $item['#type']))); + } + + // Verify special element #type text-format. + $element = $this->xpath('//div[contains(@class, :div-class)]/descendant::textarea[@name=:name]', array( + ':name' => 'text_format[value]', + ':div-class' => 'form-disabled', + )); + $this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => 'text_format[value]'))); + $element = $this->xpath('//div[contains(@class, :div-class)]/descendant::select[@name=:name]', array( + ':name' => 'text_format[format]', + ':div-class' => 'form-disabled', + )); + $this->assertTrue(isset($element[0]), t('Disabled form element class found for #type %type.', array('%type' => 'text_format[format]'))); + } + /** * Test Form API protections against input forgery. * diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 2fb4937dec33..ab0642fee81d 100644 --- a/modules/simpletest/tests/form_test.module +++ b/modules/simpletest/tests/form_test.module @@ -882,6 +882,64 @@ function _form_test_disabled_elements($form, &$form_state) { ); } + // Text format. + $form['text_format'] = array( + '#type' => 'text_format', + '#title' => 'Text format', + '#disabled' => TRUE, + '#default_value' => 'Text value', + '#format' => 1, + '#expected_value' => array( + 'value' => 'Text value', + 'format' => 1, + ), + '#test_hijack_value' => array( + 'value' => 'HIJACK', + 'format' => 2, + ), + ); + + // Password fields. + $form['password'] = array( + '#type' => 'password', + '#title' => 'Password', + '#disabled' => TRUE, + ); + $form['password_confirm'] = array( + '#type' => 'password_confirm', + '#title' => 'Password confirm', + '#disabled' => TRUE, + ); + + // Files. + $form['file'] = array( + '#type' => 'file', + '#title' => 'File', + '#disabled' => TRUE, + ); + $form['managed_file'] = array( + '#type' => 'managed_file', + '#title' => 'Managed file', + '#disabled' => TRUE, + ); + + // Buttons. + $form['image_button'] = array( + '#type' => 'image_button', + '#value' => 'Image button', + '#disabled' => TRUE, + ); + $form['button'] = array( + '#type' => 'button', + '#value' => 'Button', + '#disabled' => TRUE, + ); + $form['submit_disabled'] = array( + '#type' => 'submit', + '#value' => 'Submit', + '#disabled' => TRUE, + ); + $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), diff --git a/themes/seven/style.css b/themes/seven/style.css index ac95e88e8e42..caf7164abacb 100644 --- a/themes/seven/style.css +++ b/themes/seven/style.css @@ -627,6 +627,13 @@ div.teaser-checkbox .form-item, .form-item label.option input { vertical-align: middle; } +.form-disabled input.form-autocomplete, +.form-disabled input.form-text, +.form-disabled textarea.form-textarea, +.form-disabled select.form-select { + background-color: #eee; + color: #777; +} /* Filter */ .filter-wrapper { @@ -699,6 +706,13 @@ input.form-submit:active { border-color: #555; text-shadow: #222 0px -1px 0px; } +input.form-button-disabled, +input.form-button-disabled:active { + background: #eee none; + border-color: #eee; + text-shadow: none; + color: #999; +} form input#edit-delete { background: #eee; border-color: #fff #ddd #ccc; -- GitLab