Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.986
diff -u -u -p -r1.986 common.inc
--- includes/common.inc	10 Sep 2009 06:38:16 -0000	1.986
+++ includes/common.inc	17 Sep 2009 17:15:57 -0000
@@ -4700,6 +4700,9 @@ function drupal_common_theme() {
     'form_element' => array(
       'arguments' => array('element' => NULL),
     ),
+    'form_element_label' => array(
+      'arguments' => array('element' => NULL),
+    ),
     'text_format_wrapper' => array(
       'arguments' => array('element' => NULL),
     ),
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.371
diff -u -u -p -r1.371 form.inc
--- includes/form.inc	10 Sep 2009 06:31:38 -0000	1.371
+++ includes/form.inc	17 Sep 2009 17:15:58 -0000
@@ -1090,6 +1090,17 @@ function form_builder($form_id, $element
     $form_state['has_file_element'] = TRUE;
   }
 
+  // Set the element's title attribute to the elements #title, if needed.
+  if (!empty($element['#title']) && isset($element['#show_title']) && $element['#show_title'] == FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE) {
+    $element['#attributes']['title'] = $element['#title'];
+    if (!empty($element['#required'])) {
+      // Add an indication that this field is required.
+      $element['#attributes']['title'] .= ' (' . $t('This field is required.') . ')'; 
+    }
+    // Unset #show_title to simplify later theming, we have done all we need.
+    unset($element['#show_title']);
+  }
+
   if (isset($element['#type']) && $element['#type'] == 'form') {
     // We are on the top form.
     // If there is a file element, we set the form encoding.
@@ -1506,8 +1517,8 @@ function form_options_flatten($array, $r
  *
  * @param $element
  *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #options, #description, #extra, #multiple,
- *   #required, #name, #attributes, #size.
+ *   Properties used: #size, #multiple, #name, #id, #value, #options,
+ *   #attributes.
  * @return
  *   A themed HTML string representing the form element.
  *
@@ -1518,7 +1529,6 @@ function form_options_flatten($array, $r
  * values are associative arrays in the normal $options format.
  */
 function theme_select($element) {
-  $select = '';
   $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
   _form_set_class($element, array('form-select'));
   $multiple = $element['#multiple'];
@@ -1653,8 +1663,7 @@ function theme_fieldset($element) {
  *
  * @param $element
  *   An associative array containing the properties of the element.
- *   Properties used: #required, #return_value, #value, #attributes, #title,
- *   #description
+ *   Properties used: #id, #name, #value, #return_value, #attributes.
  * @return
  *   A themed HTML string representing the form item group.
  *
@@ -1668,9 +1677,6 @@ function theme_radio($element) {
   $output .= 'value="' . $element['#return_value'] . '" ';
   $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
   $output .= drupal_attributes($element['#attributes']) . ' />';
-  if (!is_null($element['#title'])) {
-    $output = '<label class="option" for="' . $element['#id'] . '">' . $output . ' ' . $element['#title'] . '</label>';
-  }
 
   return $output;
 }
@@ -1989,8 +1995,7 @@ function theme_text_format_wrapper($elem
  *
  * @param $element
  *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #return_value, #description, #required,
- *   #attributes.
+ *   Properties used: #name, #id, #value, #return_value, #attributes.
  * @return
  *   A themed HTML string representing the checkbox.
  *
@@ -2006,10 +2011,6 @@ function theme_checkbox($element) {
   $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
   $checkbox .= drupal_attributes($element['#attributes']) . ' />';
 
-  if (!is_null($element['#title'])) {
-    $checkbox = '<label class="option" for="' . $element['#id'] . '">' . $checkbox . ' ' . $element['#title'] . '</label>';
-  }
-
   return $checkbox;
 }
 
@@ -2570,7 +2571,8 @@ function theme_file($element) {
  *
  * @param element
  *   An associative array containing the properties of the element.
- *   Properties used: #title, #description, #id, #required, #children
+ *   Properties used: #type, #name, #title, #show_title, #children,
+ *   #description
  * @return
  *   A string representing the form element.
  *
@@ -2590,19 +2592,19 @@ function theme_form_element($element) {
   }
 
   $output = '<div class="' . implode(' ', $class) . '">' . "\n";
-  $required = !empty($element['#required']) ? '<span class="form-required" title="' . $t('This field is required.') . '">*</span>' : '';
 
-  if (!empty($element['#title']) && empty($element['#form_element_skip_title'])) {
-    $title = $element['#title'];
-    if (!empty($element['#id'])) {
-      $output .= ' <label for="' . $element['#id'] . '">' . $t('!title !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
-    }
-    else {
-      $output .= ' <label>' . $t('!title !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
-    }
+  // Place label & required mark in correct position, depending on #show_title.
+  if (isset($element['#show_title']) && $element['#show_title'] == FORM_ELEMENT_SHOW_TITLE_BEFORE) {
+    $output .= theme('form_element_label', $element) . ' ' . $element['#children'] . "\n";
+  }
+  else {
+    // In all other cases (#show_title is FORM_ELEMENT_SHOW_TITLE_BEFORE,
+    // FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE or is not set) we add the label and
+    // required mark after the element. In the last 2 cases this ensures the
+    // required mark is still present to indicate the field is required even
+    // through there is no label added.
+    $output .= $element['#children'] . ' ' . theme('form_element_label', $element) . "\n";
   }
-
-  $output .= " " . $element['#children'] . "\n";
 
   if (!empty($element['#description'])) {
     $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
@@ -2614,6 +2616,39 @@ function theme_form_element($element) {
 }
 
 /**
+ * Theme a form element label and required mark.
+ *
+ * @param element
+ *   An associative array containing the properties of the element.
+ *   Properties used: #required, #show_title, #title, #label_option, #id
+ * @return
+ *   A string representing the form element label and/or required mark.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element_label($element) {
+  // This is also used in the installer, pre-database setup.
+  $t = get_t();
+
+  $required = !empty($element['#required']) ? '<span class="form-required" title="' . $t('This field is required.') . '">*</span>' : '';
+  // If there is no title,  or the title is not set to display we simply
+  // return any required mark here.
+  if (empty($element['#title']) || empty($element['#show_title'])) {
+    return $required;
+  }
+
+  $attributes = array();
+  if (isset($element['#label_option']) && $element['#label_option']) {
+    // This class styles the label as an option, inline with the element.
+    $attributes['class'] = 'option';
+  }
+  if (!empty($element['#id'])) {
+    $attributes['for'] = $element['#id'];
+  }
+  return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => filter_xss_admin($element['#title']), '!required' => $required)) . "</label>\n";
+}
+
+/**
  * Sets a form element's class attribute.
  *
  * Adds 'required' and 'error' classes as needed.
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.14
diff -u -u -p -r1.14 form.test
--- modules/simpletest/tests/form.test	13 Jul 2009 21:51:41 -0000	1.14
+++ modules/simpletest/tests/form.test	17 Sep 2009 17:15:58 -0000
@@ -112,6 +112,80 @@ class FormsTestTypeCase extends DrupalUn
 }
 
 /**
+ * Test the form elements, labels and associated output for correctness.
+ */
+class FormsElementsLabelsTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Form element and label output test',
+      'description' => 'Test the form elements, labels and associated output for correctness.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  /**
+   * Test form elements, labels, title attibutes and required marks output
+   * correctly and have the correct label option class if needed.
+   */
+  function testFormLabels() {
+    $this->drupalGet('form_test/form-labels');
+
+    // Check that the checkbox/radio processing is not interfering with
+    // basic placement.
+    $elements = $this->xpath('//input[@id="edit-form-checkboxes-test-thirdcheckbox"]/following-sibling::label[@for="edit-form-checkboxes-test-thirdcheckbox" and @class="option"]');
+    $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular checkboxes."));
+
+    $elements = $this->xpath('//input[@id="edit-form-radios-test-secondradio"]/following-sibling::label[@for="edit-form-radios-test-secondradio" and @class="option"]');
+    $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular radios."));
+
+    // Exercise various defaults for checkboxes and modifications to ensure
+    // appropriate override and correct behaviour.
+    $elements = $this->xpath('//input[@id="edit-form-checkbox-test"]/following-sibling::label[@for="edit-form-checkbox-test" and @class="option"]');
+    $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for a checkbox."));
+
+    $elements = $this->xpath('//input[@id="edit-form-checkbox-test-attribute" and @title="Checkbox test with label as attribute"]');
+    $this->assertTrue(isset($elements[0]), t("Title as attribute for a checkbox is correct."));
+
+    $elements = $this->xpath('//input[@id="edit-form-checkbox-test-attribute"]/following-sibling::label');
+    $this->assertFalse(isset($elements[0]), t("Label does not follow title as attribute for a checkbox."));
+
+    $elements = $this->xpath('//input[@id="edit-form-checkbox-test-no-label-option"]/following-sibling::label[@for="edit-form-checkbox-test-no-label-option"]');
+    $this->assertTrue(isset($elements[0]), t("Label follows field for a checkbox without a label option class."));
+
+    $elements = $this->xpath('//input[@id="edit-form-checkbox-test-no-label-option"]/following-sibling::label[@for="edit-form-checkbox-test-no-label-option" and @class="option"]');
+    $this->assertFalse(isset($elements[0]), t("No label option class found for checkbox without a label option class."));
+
+    // Exercise various defaults for textboxes and modifications to ensure
+    // appropriate override and correct behaviour.
+    $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required"]/child::span[@class="form-required"]/parent::*/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
+    $this->assertTrue(isset($elements[0]), t("Label preceeds textfield, with required marker inside label."));
+
+    $elements = $this->xpath('//input[@id="edit-form-textfield-test-no-title-required"]/preceding-sibling::span[@class="form-required"]');
+    $this->assertTrue(isset($elements[0]), t("Required marker when required, preceeds textfield."));
+
+    $elements = $this->xpath('//label[@for="edit-form-textfield-test-no-title-required"]');
+    $this->assertFalse(isset($elements[0]), t("No label tag when no title set on field."));
+
+    $elements = $this->xpath('//input[@id="edit-form-textfield-test-title"]/preceding-sibling::span[@class="form-required"]');
+    $this->assertFalse(isset($elements[0]), t("No required marker on non-required field."));
+
+    $elements = $this->xpath('//input[@id="edit-form-textfield-test-title-attribute" and @title="Textfield test for title as attribute"]');
+    $this->assertTrue(isset($elements[0]), t("Title as attribute for a textfield is correct."));
+
+    $elements = $this->xpath('//input[@id="edit-form-textfield-test-title-after-option"]/following-sibling::label[@for="edit-form-textfield-test-title-after-option" and @class="option"]');
+    $this->assertTrue(isset($elements[0]), t("Label preceeds field and label option class correct for text field when set."));
+
+    $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-no-show"]');
+    $this->assertFalse(isset($elements[0]), t("No label tag when title set not to display."));
+  }
+}
+
+/**
  * Test the tableselect form element for expected behavior.
  */
 class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.8
diff -u -u -p -r1.8 form_test.module
--- modules/simpletest/tests/form_test.module	17 Aug 2009 07:12:16 -0000	1.8
+++ modules/simpletest/tests/form_test.module	17 Sep 2009 17:15:58 -0000
@@ -66,6 +66,14 @@ function form_test_menu() {
     'type' => MENU_CALLBACK,
   );
 
+  $items['form_test/form-labels'] = array(
+    'title' => 'Form label test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_label_test_form'),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
   return $items;
 }
 
@@ -365,3 +373,74 @@ function form_storage_test_form_submit($
   $form_state['storage']['step']++;
   drupal_set_message("Form constructions: ". $_SESSION['constructions']);
 }
+
+/**
+ * A form for testing form labels and required marks.
+ */
+function form_label_test_form(&$form_state) {
+  $form['form_checkboxes_test'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Checkboxes test'),
+    '#options' => array(
+      'firstcheckbox' => t('First Checkbox'),
+      'secondcheckbox' => t('Second Checkbox'),
+      'thirdcheckbox' => t('Third Checkbox'),
+    ),
+  );
+  $form['form_radios_test'] = array(
+    '#type' => 'radios',
+    '#title' => t('Radios test'),
+    '#options' => array(
+      'firstradio' => t('First Radio'),
+      'secondradio' => t('Second Radio'),
+      'thirdradio' => t('Third Radio'),
+    ),
+  );
+  $form['form_checkbox_test'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Checkbox test'),
+  );
+  $form['form_checkbox_test_attribute'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Checkbox test with label as attribute'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE,
+  );
+  $form['form_checkbox_test_no_label_option'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Checkbox test with label not styled as an option'),
+    '#label_option' => FALSE,
+  );
+  $form['form_textfield_test_title_and_required'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Textfield test for required with title'),
+    '#required' => TRUE,
+  );
+  $form['form_textfield_test_no_title_required'] = array(
+    '#type' => 'textfield',
+    // No title.
+    '#required' => TRUE,
+  );
+  $form['form_textfield_test_title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Textfield test for title only'),
+    // Not required.
+  );
+  $form['form_textfield_test_title_attribute'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Textfield test for title as attribute'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE,
+  );
+  $form['form_textfield_test_title_after_option'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Textfield test for title after element with label styled as an option'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+    '#label_option' => TRUE,
+  );
+  $form['form_textfield_test_title_no_show'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Textfield test for title set not to display'),
+    '#show_title' => FALSE,
+  );
+  
+  return $form;
+}
\ No newline at end of file
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.74
diff -u -u -p -r1.74 system.api.php
--- modules/system/system.api.php	10 Sep 2009 06:38:20 -0000	1.74
+++ modules/system/system.api.php	17 Sep 2009 17:15:58 -0000
@@ -215,6 +215,9 @@ function hook_db_rewrite_sql($query, $pr
  *  - "#pre_render": array of callback functions taking $element and $form_state.
  *  - "#post_render": array of callback functions taking $element and $form_state.
  *  - "#submit": array of callback functions taking $form and $form_state.
+ *  - "#show_title": optionally one of the FORM_ELEMENT_SHOW_TITLE_* constants
+ *    indicating if and how the #title should be displayed.
+ *  - "#label_option": add a class to style the element label as an option.
  *
  * @see hook_element_info_alter()
  * @see system_element_info()
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.788
diff -u -u -p -r1.788 system.module
--- modules/system/system.module	10 Sep 2009 06:32:54 -0000	1.788
+++ modules/system/system.module	17 Sep 2009 17:15:58 -0000
@@ -86,6 +86,27 @@ define('REGIONS_VISIBLE', 'visible');
  */
 define('REGIONS_ALL', 'all');
 
+/**
+ *
+ * Output form element titles as labels before form elements. 
+ * @see system_elements().
+ */
+define('FORM_ELEMENT_SHOW_TITLE_BEFORE', 'before');
+
+/**
+ *
+ * Output form element titles as labels after form elements. 
+ * @see system_elements().
+ */
+define('FORM_ELEMENT_SHOW_TITLE_AFTER', 'after');
+
+/**
+ *
+ * Output form element titles as the title attribute of form elements. 
+ * @see system_elements().
+ */
+define('FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE', 'attribute');
+
 
 /**
  * Implement hook_help().
@@ -334,6 +355,7 @@ function system_element_info() {
     '#process' => array('form_process_text_format', 'ajax_process_form'),
     '#theme' => 'textfield',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['password'] = array(
     '#input' => TRUE,
@@ -342,11 +364,13 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'password',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['password_confirm'] = array(
     '#input' => TRUE,
     '#process' => array('form_process_password_confirm'),
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['textarea'] = array(
     '#input' => TRUE,
@@ -356,12 +380,14 @@ function system_element_info() {
     '#process' => array('form_process_text_format', 'ajax_process_form'),
     '#theme' => 'textarea',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['radios'] = array(
     '#input' => TRUE,
     '#process' => array('form_process_radios'),
     '#theme_wrappers' => array('radios'),
     '#pre_render' => array('form_pre_render_conditional_form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['radio'] = array(
     '#input' => TRUE,
@@ -369,7 +395,8 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'radio',
     '#theme_wrappers' => array('form_element'),
-    '#form_element_skip_title' => TRUE,
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+    '#label_option' => TRUE,
   );
   $types['checkboxes'] = array(
     '#input' => TRUE,
@@ -377,6 +404,7 @@ function system_element_info() {
     '#process' => array('form_process_checkboxes'),
     '#theme_wrappers' => array('checkboxes'),
     '#pre_render' => array('form_pre_render_conditional_form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['checkbox'] = array(
     '#input' => TRUE,
@@ -384,7 +412,8 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'checkbox',
     '#theme_wrappers' => array('form_element'),
-    '#form_element_skip_title' => TRUE,
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+    '#label_option' => TRUE,
   );
   $types['select'] = array(
     '#input' => TRUE,
@@ -393,6 +422,7 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'select',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['weight'] = array(
     '#input' => TRUE,
@@ -406,12 +436,14 @@ function system_element_info() {
     '#process' => array('form_process_date'),
     '#theme' => 'date',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['file'] = array(
     '#input' => TRUE,
     '#size' => 60,
     '#theme' => 'file',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['tableselect'] = array(
     '#input' => TRUE,
@@ -428,6 +460,7 @@ function system_element_info() {
     '#markup' => '',
     '#theme' => 'markup',
     '#theme_wrappers' => array('form_element'),
+    '#show_title' => FORM_ELEMENT_SHOW_TITLE_BEFORE,
   );
   $types['hidden'] = array(
     '#input' => TRUE,
Index: modules/user/user.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v
retrieving revision 1.51
diff -u -u -p -r1.51 user.pages.inc
--- modules/user/user.pages.inc	10 Sep 2009 12:33:46 -0000	1.51
+++ modules/user/user.pages.inc	17 Sep 2009 17:15:59 -0000
@@ -460,7 +460,6 @@ function user_cancel_methods() {
       '#return_value' => $name,
       '#default_value' => $default_method,
       '#parents' => array('user_cancel_method'),
-      '#required' => TRUE,
     );
   }
   return $form;
