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