Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.174.2.13
diff -u -p -r1.174.2.13 form.inc
--- includes/form.inc	27 Dec 2007 08:41:52 -0000	1.174.2.13
+++ includes/form.inc	6 Jun 2008 01:19:30 -0000
@@ -251,6 +251,14 @@ function drupal_process_form($form_id, &
   drupal_prepare_form($form_id, $form);
   if (($form['#programmed']) || (!empty($_POST) && (($_POST['form_id'] == $form_id)))) {
     drupal_validate_form($form_id, $form);
+
+    // form_clean_id() maintains a cache of element IDs it has seen,
+    // so it can prevent duplicates. We want to be sure we reset that
+    // cache when a form is processed, so scenerios that result in
+    // the form being built behind the scenes and again for the
+    // browser don't increment all the element IDs needlessly.
+    form_clean_id(NULL, TRUE);
+
     // IE does not send a button value when there is only one submit button (and no non-submit buttons)
     // and you submit by pressing enter.
     // In that case we accept a submission without button values.
@@ -1430,7 +1438,7 @@ function theme_textfield($element) {
 function theme_form($element) {
   // Anonymous div to satisfy XHTML compliance.
   $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
-  return '<form '. $action .' accept-charset="UTF-8" method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
+  return '<form '. $action .' accept-charset="UTF-8" method="'. $element['#method'] .'" '. 'id="'. form_clean_id($element['#id']) .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
 }
 
 /**
@@ -1585,15 +1593,43 @@ function _form_set_class(&$element, $cla
 }
 
 /**
- * Remove invalid characters from an HTML ID attribute string.
+ * Prepare an HTML ID attribute string for a form item.
+ *
+ * Remove invalid characters and guarantee uniqueness.
  *
  * @param $id
  *   The ID to clean.
+ * @param $flush
+ *   If set to TRUE, the function will flush and reset the static array
+ *   which is built to test the uniqueness of element IDs. This is only
+ *   used if a form has completed the validation process. This parameter
+ *   should never be set to TRUE if this function is being called to
+ *   assign an ID to the #ID element.
  * @return
  *   The cleaned ID.
  */
-function form_clean_id($id = NULL) {
+function form_clean_id($id = NULL, $flush = FALSE) {
+  static $seen_ids = array();
+
+  if ($flush) {
+    $seen_ids = array();
+    return;
+  }
   $id = str_replace(array('][', '_', ' '), '-', $id);
+
+  // Ensure IDs are unique. The first occurrence is held but left alone.
+  // Subsequent occurrences get a number appended to them. This incrementing
+  // will almost certainly break code that relies on explicit HTML IDs in
+  // forms that appear more than once on the page, but the alternative is
+  // outputting duplicate IDs, which would break JS code and XHTML
+  // validity anyways. For now, it's an acceptable stopgap solution.
+  if (isset($seen_ids[$id])) {
+    $id = $id .'-'. $seen_ids[$id]++;
+  }
+  else {
+    $seen_ids[$id] = 1;
+  }
+
   return $id;
 }
 
