Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.541
diff -u -p -r1.541 book.module
--- modules/book/book.module	1 May 2010 08:12:22 -0000	1.541
+++ modules/book/book.module	4 May 2010 19:50:04 -0000
@@ -403,7 +403,7 @@ function book_get_books() {
 function book_form_alter(&$form, &$form_state, $form_id) {
   if (!empty($form['#node_edit_form'])) {
     // Add elements to the node form.
-    $node = $form['#node'];
+    $node = $form_state['node'];
 
     $access = user_access('administer book outlines');
     if (!$access) {
@@ -415,15 +415,13 @@ function book_form_alter(&$form, &$form_
 
     if ($access) {
       _book_add_form_elements($form, $form_state, $node);
+      // Since the "Book" dropdown can't trigger a form submission when
+      // JavaScript is disabled, add a submit button to do that. book.css hides
+      // this button when JavaScript is enabled.
       $form['book']['pick-book'] = array(
         '#type' => 'submit',
         '#value' => t('Change book (update list of parents)'),
-         // Submit the node form so the parent select options get updated.
-         // This is typically only used when JS is disabled. Since the parent options
-         // won't be changed via AJAX, a button is provided in the node form to submit
-         // the form and generate options in the parent select corresponding to the
-         // selected book. This is similar to what happens during a node preview.
-        '#submit' => array('node_form_submit_build_node'),
+        '#submit' => array('book_pick_book_nojs_submit'),
         '#weight' => 20,
       );
     }
@@ -431,6 +429,22 @@ function book_form_alter(&$form, &$form_
 }
 
 /**
+ * Submit handler to change a node's book.
+ *
+ * This handler is run when JavaScript is disabled. It triggers the form to
+ * rebuild so that the "Parent item" options are changed to reflect the newly
+ * selected book. When JavaScript is enabled, the submit button that triggers
+ * this handler is hidden, and the "Book" dropdown directly triggers the
+ * book_form_update() AJAX callback instead.
+ *
+ * @see book_form_update()
+ */
+function book_pick_book_nojs_submit($form, &$form_state) {
+  $form_state['node']->book = $form_state['values']['book'];
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
  * Build the parent selection form element for the node form or outline tab.
  *
  * This function is also called when generating a new set of options during the
Index: modules/field/field.form.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v
retrieving revision 1.48
diff -u -p -r1.48 field.form.inc
--- modules/field/field.form.inc	30 Apr 2010 08:07:54 -0000	1.48
+++ modules/field/field.form.inc	4 May 2010 19:50:05 -0000
@@ -368,17 +368,22 @@ function field_default_form_errors($enti
  * to return just the changed part of the form.
  */
 function field_add_more_submit($form, &$form_state) {
-  // Set the form to rebuild and run submit handlers.
+  // @todo Legacy entity forms assume that #builder_function is called from all
+  //   button-level submit handlers that trigger a rebuild. This is poor
+  //   encapsulation and can lead to security vulnerabilities when used in
+  //   conjunction with #limit_validation_errors. Node forms have been fixed,
+  //   but some entity forms still rely on this. Remove this once all entity
+  //   forms have been fixed: http://drupal.org/node/735800.
   if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) {
-    $entity = $form['#builder_function']($form, $form_state);
-
-    // Make the changes we want to the form state.
-    $field_name = $form_state['clicked_button']['#field_name'];
-    $langcode = $form_state['clicked_button']['#language'];
-    if ($form_state['values'][$field_name . '_add_more']) {
-      $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$langcode]);
-    }
+    $form['#builder_function']($form, $form_state);
+  }
+  // Make the changes we want to the form state and trigger a rebuild.
+  $field_name = $form_state['clicked_button']['#field_name'];
+  $langcode = $form_state['clicked_button']['#language'];
+  if ($form_state['values'][$field_name . '_add_more']) {
+    $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$langcode]);
   }
+  $form_state['rebuild'] = TRUE;
 }
 
 /**
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.66
diff -u -p -r1.66 node.api.php
--- modules/node/node.api.php	26 Mar 2010 17:14:45 -0000	1.66
+++ modules/node/node.api.php	4 May 2010 19:50:06 -0000
@@ -681,6 +681,27 @@ function hook_node_validate($node, $form
 }
 
 /**
+ * This hook is called whenever a node form is submitted.
+ *
+ * Modules may use this hook to extract their data from $form_state and set it
+ * on the $node object. Note that every key/value pair in $form_state['values']
+ * is automatically added as a corresponding property/value pair in $node, so
+ * modules only need to implement this hook to do something different than that.
+ *
+ * @param $node
+ *   The node being built from data in $form_state.
+ * @param $form
+ *   The form being used to edit the node.
+ * @param $form_state
+ *   The form state array.  
+ *
+ * @ingroup node_api_hooks
+ */
+function hook_node_submit($node, $form, $form_state) {
+  // @todo Need an example.
+}
+
+/**
  * Act on a node that is being assembled before rendering.
  *
  * The module may add elements to $node->content prior to rendering. This hook
Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.123
diff -u -p -r1.123 node.pages.inc
--- modules/node/node.pages.inc	24 Apr 2010 14:49:14 -0000	1.123
+++ modules/node/node.pages.inc	4 May 2010 19:50:07 -0000
@@ -75,7 +75,13 @@ function node_add($type) {
   return $output;
 }
 
+/**
+ * Validate the node add/edit form.
+ */
 function node_form_validate($form, &$form_state) {
+  // @todo Legacy support. For Drupal 8, implement a clear and consistent
+  //   distinction between form validation and entity validation, rather than
+  //   casting arbitrary form values to mock entities.
   $node = (object)$form_state['values'];
   node_validate($node, $form);
 
@@ -89,14 +95,17 @@ function node_form_validate($form, &$for
  */
 function node_form($form, &$form_state, $node) {
   global $user;
-  // This form has its own multistep persistence.
-  if ($form_state['rebuild']) {
-    $form_state['input'] = array();
-  }
 
+  // If the form is being rebuilt after a submit handler updated
+  // $form_state['node'], use that. Otherwise, initialize $form_state['node']
+  // with the node object passed as a build argument.
   if (isset($form_state['node'])) {
-    $node = (object)($form_state['node'] + (array)$node);
+    $node = $form_state['node'];
   }
+  else {
+    $form_state['node'] = $node;
+  }
+
   if (isset($form_state['node_preview'])) {
     $form['#prefix'] = $form_state['node_preview'];
   }
@@ -141,7 +150,8 @@ function node_form($form, &$form_state, 
     $form['title']['#weight'] = -5;
   }
 
-  $form['#node'] = $node;
+  // @todo Legacy support. Remove in Drupal 8.
+  $form['#node'] = $form_state['node'];
 
   $form['additional_settings'] = array(
     '#type' => 'vertical_tabs',
@@ -273,7 +283,6 @@ function node_form($form, &$form_state, 
   $form['#validate'][] = 'node_form_validate';
   $form['#theme'] = array($node->type . '_node_form', 'node_form');
 
-  $form['#builder_function'] = 'node_form_submit_build_node';
   field_attach_form('node', $node, $form, $form_state, $node->language);
 
   return $form;
@@ -296,6 +305,7 @@ function node_form_delete_submit($form, 
 function node_form_build_preview($form, &$form_state) {
   $node = node_form_submit_build_node($form, $form_state);
   $form_state['node_preview'] = node_preview($node);
+  $form_state['rebuild'] = TRUE;
 }
 
 /**
@@ -393,7 +403,6 @@ function node_form_submit($form, &$form_
     drupal_set_message(t('@type %title has been updated.', $t_args));
   }
   if ($node->nid) {
-    unset($form_state['rebuild']);
     $form_state['values']['nid'] = $node->nid;
     $form_state['nid'] = $node->nid;
     $form_state['redirect'] = 'node/' . $node->nid;
@@ -402,25 +411,38 @@ function node_form_submit($form, &$form_
     // In the unlikely case something went wrong on save, the node will be
     // rebuilt and node form redisplayed the same way as in preview.
     drupal_set_message(t('The post could not be saved.'), 'error');
+    $form_state['rebuild'] = TRUE;
   }
   // Clear the page and block caches.
   cache_clear_all();
 }
 
 /**
- * Build a node by processing submitted form values and prepare for a form rebuild.
+ * Build a node from what was submitted on the add/edit form by processing the submitted form values.
+ *
+ * This function invokes form-level submit handlers, so only call it if the form
+ * has been fully validated (do not call it from button-level submit handlers
+ * of buttons that use #limit_validation_errors).
  */
 function node_form_submit_build_node($form, &$form_state) {
-  // Unset any button-level handlers, execute all the form-level submit
-  // functions to process the form values into an updated node.
+  // Process the form values into an updated node by executing all the
+  // form-level submit handlers, which requires unsetting the button-level ones.
   unset($form_state['submit_handlers']);
   form_execute_handlers('submit', $form, $form_state);
-  $node = node_submit((object)$form_state['values']);
+  $node = (object)($form_state['values'] + (array)$form_state['node']);
 
+  // Execute all entity-level and field-level submit hooks.
+  $node = node_submit($node);
   field_attach_submit('node', $node, $form, $form_state);
+  foreach (module_implements('node_submit') as $module) {
+    $function = $module . '_node_submit';
+    $function($node, $form, $form_state);
+  }
 
-  $form_state['node'] = (array)$node;
-  $form_state['rebuild'] = TRUE;
+  // There may be additional submit handlers that run after the one that calls
+  // this function, or one of the submit handlers may trigger a form rebuild, so
+  // store the built node in $form_state in addition to returning it.
+  $form_state['node'] = $node;
   return $node;
 }
 
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.346
diff -u -p -r1.346 poll.module
--- modules/poll/poll.module	30 Apr 2010 08:07:55 -0000	1.346
+++ modules/poll/poll.module	4 May 2010 19:50:07 -0000
@@ -352,15 +352,18 @@ function poll_form($node, &$form_state) 
  * return just the changed part of the form.
  */
 function poll_more_choices_submit($form, &$form_state) {
-  include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'node') . '/node.pages.inc';
-  // Set the form to rebuild and run submit handlers.
-  node_form_submit_build_node($form, $form_state);
-
   // Make the changes we want to the form state.
   if ($form_state['values']['poll_more']) {
     $n = $_GET['q'] == 'system/ajax' ? 1 : 5;
     $form_state['choice_count'] = count($form_state['values']['choice']) + $n;
   }
+  // Renumber the choices. This invalidates the corresponding key/value
+  // associations in $form_state['input'], so clear that out. This requires
+  // poll_form() to rebuild the choices with the values in
+  // $form_state['node']->choice, which it does.
+  $form_state['node']->choice = array_values($form_state['values']['choice']);
+  unset($form_state['input']['choice']);
+  $form_state['rebuild'] = TRUE;
 }
 
 function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) {
