Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.288
diff -u -r1.288 poll.module
--- modules/poll/poll.module	3 Feb 2009 18:55:31 -0000	1.288
+++ modules/poll/poll.module	7 Feb 2009 05:27:26 -0000
@@ -258,6 +258,8 @@
     '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."),
     '#weight' => 1,
     '#submit' => array('poll_more_choices_submit'), // If no javascript action.
+    '#validate' => array(), // No validation needed for the more button.
+    '#validate_portion' => array('poll_more_validate_portion'),
     '#ahah' => array(
       'callback' => 'poll_choice_js',
       'wrapper' => 'poll-choices',
@@ -304,7 +306,9 @@
  */
 function poll_more_choices_submit($form, &$form_state) {
   // Set the form to rebuild and run submit handlers.
-  node_form_submit_build_node($form, $form_state);
+  if (drupal_function_exists('node_form_submit_build_node')) {
+    node_form_submit_build_node($form, $form_state);
+  }
 
   // Make the changes we want to the form state.
   if ($form_state['values']['poll_more']) {
@@ -313,6 +317,13 @@
   }
 }
 
+/**
+ * Validate portion callback. Only validate the poll portion of the form.
+ */
+function poll_more_validate_portion($form, &$form_state) {
+  return $form['choice_wrapper']['choice'];
+}
+
 function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) {
   $admin = user_access('administer nodes');
 
Index: modules/poll/poll.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.test,v
retrieving revision 1.15
diff -u -r1.15 poll.test
--- modules/poll/poll.test	1 Feb 2009 06:48:15 -0000	1.15
+++ modules/poll/poll.test	7 Feb 2009 05:27:26 -0000
@@ -178,11 +178,7 @@
     $web_user = $this->drupalCreateUser(array('create poll content', 'access content'));
     $this->drupalLogin($web_user);
     $this->drupalGet('node/add/poll');
-    $edit = array(
-      'title' => $this->randomName(),
-      'choice[new:0][chtext]' => $this->randomName(),
-      'choice[new:1][chtext]' => $this->randomName(),
-    );
+    $edit = array();
 
     // @TODO: the framework should make it possible to submit a form to a
     // different URL than its action or the current. For now, we can just force
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.3
diff -u -r1.3 form.test
--- modules/simpletest/tests/form.test	28 Jan 2009 07:43:26 -0000	1.3
+++ modules/simpletest/tests/form.test	7 Feb 2009 05:27:27 -0000
@@ -23,7 +23,7 @@
    */
   function testRequiredFields() {
     // Originates from http://drupal.org/node/117748
-    // Sets of empty strings and arrays
+    // Sets of empty strings and arrays.
     $empty_strings = array('""' => "", '"\n"' => "\n", '" "' => " ", '"\t"' => "\t", '" \n\t "' => " \n\t ", '"\n\n\n\n\n"' => "\n\n\n\n\n");
     $empty_arrays = array('array()' => array());
 
@@ -51,7 +51,7 @@
     $elements['file']['element'] = array('#title' => $this->randomName(), '#type' => 'file', '#required' => TRUE);
     $elements['file']['empty_values'] = $empty_strings;
 
-    // Go through all the elements and all the empty values for them
+    // Go through all the elements and all the empty values for them.
     foreach ($elements as $type => $data) {
       foreach ($data['empty_values'] as $key => $empty) {
         $form_id = $this->randomName();
@@ -71,6 +71,94 @@
     // Clear the expected form error messages so they don't appear as exceptions.
     drupal_get_messages();
   }
+
+  /**
+   * Validate a portion of a form but not others.
+   *
+   * If the validated form field is found in form_get_errors() and the
+   * non-validated field is not found then the test pass.
+   */
+  function testValidatePortion() {
+    /**
+     * Sample #validate_portion function used in this test.
+     */
+    function test_validate_portion($form, $form_state) {
+      $portion = array();
+      foreach ($form['#validate_keys'] as $key) {
+        $portion[$key] = $form[$key];
+      }
+      return $portion;
+    }
+
+    // A list of elements that will intentionally fail validation.
+    $validated = array(
+      $this->randomName(),
+      $this->randomName(),
+      $this->randomName(),
+    );
+
+    // A list of elements that will intentionally be skipped in validation.
+    $non_validated = array(
+      $this->randomName(),
+    );
+
+    // A fieldset to test test validation within a nested field.
+    $fieldset = $this->randomName();
+
+    $form = array(
+      '#validate_keys' => array($fieldset, $validated[0]),
+    );
+    $form[$validated[0]] = array(
+      '#type' => 'textfield',
+      '#required' => TRUE,
+    );
+    $form[$non_validated[0]] = array(
+      '#type' => 'textfield',
+      '#required' => TRUE,
+    );
+    $form[$fieldset] = array(
+      '#type' => 'fieldset',
+      '#tree' => TRUE,
+    );
+    $form[$fieldset][$validated[1]] = array(
+      '#type' => 'textfield',
+      '#required' => TRUE,
+    );
+    $form[$fieldset][$this->randomName()][$validated[2]] = array(
+      '#type' => 'textfield',
+      '#required' => TRUE,
+    );
+    $form['op'] = array(
+      '#type' => 'submit',
+      '#value' => t('Submit'),
+      '#validate_portion' => array('test_validate_portion'),
+    );
+
+    $form_id = $this->randomName();
+    $form_state = array();
+    $form_state['values'] = array(
+      'op' => t('Submit'),
+    );
+    $form['#post'] = $form_state['values'];
+    $form['#post']['form_id'] = $form_id;
+    drupal_prepare_form($form_id, $form, $form_state);
+    drupal_process_form($form_id, $form, $form_state);
+    $errors = form_get_errors();
+
+    foreach ($validated as $element) {
+      foreach ($errors as $element_tree => $error) {
+        if (strpos($element_tree, $element) !== FALSE) {
+          $this->assertTrue(isset($errors[$element_tree]), "Partially validated form element $element_tree validated.");
+        }
+      }
+    }
+    foreach ($non_validated as $element) {
+      $this->assertFalse(isset($errors[$element]), "Partially validated form element $element intentionally not validated.");
+    }
+
+    // Clear the expected form error messages so they don't appear as exceptions.
+    drupal_get_messages();
+  }
 }
 
 /**
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.320
diff -u -r1.320 form.inc
--- includes/form.inc	3 Feb 2009 18:55:29 -0000	1.320
+++ includes/form.inc	7 Feb 2009 05:27:26 -0000
@@ -581,7 +581,21 @@
     }
   }
 
-  _form_validate($form, $form_state, $form_id);
+  // Check if a section function is specified to validate only a portion of
+  // the form using the built-in validation.
+  $portions = form_execute_handlers('validate_portion', $form, $form_state);
+
+  // If no portions are specified, validate the entire form.
+  if (empty($portions)) {
+    $portions = $form;
+  }
+
+  // Validate individual #element_validate, #required, and #options properties.
+  _form_validate($form, $form_state, $portions);
+
+  // Call user-defined form level validators.
+  form_execute_handlers('validate', $form, $form_state);
+
   $validated_forms[$form_id] = TRUE;
 }
 
@@ -645,8 +659,8 @@
  * completed, #maxlength is not exceeded, and selected options were in the
  * list of options given to the user. Then calls user-defined validators.
  *
- * @param $elements
- *   An associative array containing the structure of the form.
+ * @param $form
+ *   An associative array containing the complete structure of the form.
  * @param $form_state
  *   A keyed array containing the current state of the form. The current
  *   user-submitted data is stored in $form_state['values'], though
@@ -657,20 +671,17 @@
  *   This technique is useful when validation requires file parsing,
  *   web service requests, or other expensive requests that should
  *   not be repeated in the submission step.
- * @param $form_id
- *   A unique string identifying the form for validation, submission,
- *   theming, and hook_form_alter functions.
+ * @param $elements
+ *   An associative array containing the portion of the form to be validated.
  */
-function _form_validate($elements, &$form_state, $form_id = NULL) {
-  static $complete_form;
-
+function _form_validate(&$form, &$form_state, &$elements) {
   // Also used in the installer, pre-database setup.
   $t = get_t();
 
   // Recurse through all children.
   foreach (element_children($elements) as $key) {
     if (isset($elements[$key]) && $elements[$key]) {
-      _form_validate($elements[$key], $form_state);
+      _form_validate($form, $form_state, $elements[$key]);
     }
   }
   // Validate the current input.
@@ -712,19 +723,12 @@
       }
     }
 
-    // Call user-defined form level validators and store a copy of the full
-    // form so that element-specific validators can examine the entire structure
-    // if necessary.
-    if (isset($form_id)) {
-      form_execute_handlers('validate', $elements, $form_state);
-      $complete_form = $elements;
-    }
     // Call any element-specific validators. These must act on the element
     // #value data.
-    elseif (isset($elements['#element_validate'])) {
+    if (isset($elements['#element_validate'])) {
       foreach ($elements['#element_validate'] as $function) {
         if (drupal_function_exists($function))  {
-          $function($elements, $form_state, $complete_form);
+          $function($elements, $form_state, $form);
         }
       }
     }
@@ -746,9 +750,12 @@
  *   A keyed array containing the current state of the form. If the user
  *   submitted the form by clicking a button with custom handler functions
  *   defined, those handlers will be stored here.
+ * @return
+ *   An array of the results of each submit handler, or simply TRUE for each
+ *   submit handler executed if the handler does not return a value.
  */
 function form_execute_handlers($type, &$form, &$form_state) {
-  $return = FALSE;
+  $return = array();
   if (isset($form_state[$type . '_handlers'])) {
     $handlers = $form_state[$type . '_handlers'];
   }
@@ -768,9 +775,9 @@
         $batch['sets'][] = array('form_submit' => $function);
       }
       else {
-        $function($form, $form_state);
+        $return[$function] = $function($form, $form_state);
       }
-      $return = TRUE;
+      $return[$function] = isset($return[$function]) ? $return[$function] : TRUE;
     }
   }
   return $return;
@@ -1050,6 +1057,9 @@
       if (isset($form['#validate'])) {
         $form_state['validate_handlers'] = $form['#validate'];
       }
+      if (isset($form['#validate_portion'])) {
+        $form_state['validate_portion_handlers'] = $form['#validate_portion'];
+      }
       if (isset($form['#submit'])) {
         $form_state['submit_handlers'] = $form['#submit'];
       }
@@ -1120,6 +1130,7 @@
       $form_state['submitted'] = TRUE;
       $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
       $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
+      $form_state['validate_portion_handlers'] = empty($button['#validate_portion']) ? NULL : $button['#validate_portion'];
       $form_state['values'][$button['#name']] = $button['#value'];
       $form_state['clicked_button'] = $button;
     }
