diff --git a/modules/book/book.module b/modules/book/book.module
index 8ed14872a800de72ebfdb6cc45d49093cdc071b2..6612ea38b9948a8caea66cbe0882364be4d95928 100644
--- a/modules/book/book.module
+++ b/modules/book/book.module
@@ -187,15 +187,18 @@ function book_load($node) {
   return db_fetch_object(db_query('SELECT * FROM {book} WHERE vid = %d', $node->vid));
 }
 
-function book_form_alter($form_id, &$form) {
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['book_node_delete_confirm_submit'] = array();
-  }
+/**
+ * Implementation of hook_insert().
+ */
+function book_insert($node) {
+  db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
 }
 
-function book_node_submit($form_id, $node) {
-  $node = (object)$node;
-  if ($node->is_new || $node->revision) {
+/**
+ * Implementation of hook_update().
+ */
+function book_update($node) {
+  if ($node->revision) {
     db_query("INSERT INTO {book} (nid, vid, parent, weight) VALUES (%d, %d, %d, %d)", $node->nid, $node->vid, $node->parent, $node->weight);
   }
   else {
@@ -203,16 +206,30 @@ function book_node_submit($form_id, $node) {
   }
 }
 
-function book_node_delete_confirm_submit($form_id, $form_values) {
-  db_query('DELETE FROM {book} WHERE nid = %d OR parent = %d', $form_values['node']->nid, $form_values['node']->nid);
+/**
+ * Implementation of hook_delete().
+ */
+function book_delete(&$node) {
+  db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
 }
 
 /**
- * Implementation of hook_form().
+ * Implementation of hook_submit().
  */
-function book_form(&$node) {
+function book_submit(&$node) {
   global $user;
+  // Set default values for non-administrators.
+  if (!user_access('administer nodes')) {
+    $node->revision = 1;
+    $book->uid = $user->uid;
+    $book->name = $user->uid ? $user->name : '';
+  }
+}
 
+/**
+ * Implementation of hook_form().
+ */
+function book_form(&$node) {
   $type = node_get_types('type', $node);
   if ($node->nid && !$node->parent && !user_access('create new books')) {
     $form['parent'] = array('#type' => 'value', '#value' => $node->parent);
@@ -248,23 +265,22 @@ function book_form(&$node) {
     '#description' => t('An explanation of the additions or updates being made to help other authors understand your motivations.'),
   );
 
-  $form['weight'] = array('#type' => 'weight',
-    '#title' => t('Weight'),
-    '#access' => user_access('administer nodes'),
-    '#default_value' => $node->weight,
-    '#delta' => 15,
-    '#weight' => 5,
-    '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
-  );
-
-  if (!user_access('administer nodes')) {
+  if (user_access('administer nodes')) {
+    $form['weight'] = array('#type' => 'weight',
+      '#title' => t('Weight'),
+      '#default_value' => $node->weight,
+      '#delta' => 15,
+      '#weight' => 5,
+      '#description' => t('Pages at a given level are ordered first by weight and then by title.'),
+    );
+  }
+  else {
     // If a regular user updates a book page, we preserve the node weight; otherwise
     // we use 0 as the default for new pages
     $form['weight'] = array(
       '#type' => 'value',
       '#value' => isset($node->weight) ? $node->weight : 0,
     );
-    $form['name'] = array('#type' => 'value', '#value' => $user->uid ? $user->name : '');
   }
 
   return $form;
@@ -465,6 +481,9 @@ function book_nodeapi(&$node, $op, $teaser, $page) {
     case 'delete revision':
       db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
       break;
+    case 'delete':
+      db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+      break;
   }
 }
 
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 0df7c1f1398271a1934febc3b247372ff12af8f1..b47ace8f81a0693901faef70c995ef5b73084cab 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -278,7 +278,6 @@ function comment_form_alter($form_id, &$form) {
   }
   elseif (isset($form['type'])) {
     if ($form['type']['#value'] .'_node_form' == $form_id) {
-      $form['#submit']['comment_node_submit'] = array();
       $node = $form['#node'];
       $form['comment_settings'] = array(
         '#type' => 'fieldset',
@@ -296,10 +295,6 @@ function comment_form_alter($form_id, &$form) {
       );
     }
   }
-
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['comment_node_delete_confirm_submit'] = array();
-  }
 }
 
 /**
@@ -318,6 +313,15 @@ function comment_nodeapi(&$node, $op, $arg = 0) {
       }
       break;
 
+    case 'insert':
+      db_query('INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) VALUES (%d, %d, NULL, %d, 0)', $node->nid, $node->created, $node->uid);
+      break;
+
+    case 'delete':
+      db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid);
+      db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
+      break;
+
     case 'update index':
       $text = '';
       $comments = db_query('SELECT subject, comment, format FROM {comments} WHERE nid = %d AND status = %d', $node->nid, COMMENT_PUBLISHED);
@@ -340,17 +344,6 @@ function comment_nodeapi(&$node, $op, $arg = 0) {
   }
 }
 
-function comment_node_delete_confirm_submit($form_id, $form_values) {
-  db_query('DELETE FROM {comments} WHERE nid = %d', $form_values['node']->nid);
-  db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $form_values['node']->nid);
-}
-
-function comment_node_submit($form_id, $node) {
-  if ($node->is_new) {
-    db_query('INSERT INTO {node_comment_statistics} (nid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) VALUES (%d, %d, NULL, %d, 0)', $node->nid, $node->created, $node->uid);
-  }
-}
-
 /**
  * Implementation of hook_user().
  *
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index 1ebc7175c50ba5dcf54dae3c0f17ae79b9a1ac8f..685c96a2f2700226164b7aba451c02ba26211126 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -231,31 +231,6 @@ function forum_form_alter($form_id, &$form) {
       unset($form['nodes']['forum']);
     }
   }
-
-  if ($form_id == 'node_delete_confirm'  && $form['node']['#value']->type == 'forum') {
-    $form['#submit']['forum_node_delete_confirm_submit'] = array();
-  }
-}
-
-/**
- * Check in particular that only a "leaf" term in the associated taxonomy
- * vocabulary is selected, not a "container" term.
- */
-function forum_node_validate($form_id, $node) {
-  $node = (object)$node;
-  if ($node->taxonomy) {
-    // Extract the node's proper topic ID.
-    $vocabulary = variable_get('forum_nav_vocabulary', '');
-    $containers = variable_get('forum_containers', array());
-    foreach ($node->taxonomy as $term) {
-      if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) {
-        if (in_array($term, $containers)) {
-          $term = taxonomy_get_term($term);
-          form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
-        }
-      }
-    }
-  }
 }
 
 /**
@@ -350,15 +325,16 @@ function forum_view(&$node, $teaser = FALSE, $page = FALSE) {
 }
 
 /**
- * An early submit handler of the forum_node_form. Runs before the node is saved.
+ * Implementation of hook_submit().
+ *
+ * Check in particular that only a "leaf" term in the associated taxonomy
+ * vocabulary is selected, not a "container" term.
  */
-function forum_node_submit_early($form_id, &$node) {
-  $node = (object)$node;
-
+function forum_submit(&$node) {
   // Make sure all fields are set properly:
   $node->icon = $node->icon ? $node->icon : '';
 
-  if (!empty($node->taxonomy)) {
+  if ($node->taxonomy) {
     // Get the forum terms from the (cached) tree
     $tree = taxonomy_get_tree(_forum_get_vid());
     if ($tree) {
@@ -379,13 +355,35 @@ function forum_node_submit_early($form_id, &$node) {
       }
     }
   }
+}
 
-  $node = (array)$node;
+/**
+ * Implementation of hook_validate().
+ *
+ * Check in particular that only a "leaf" term in the associated taxonomy
+ * vocabulary is selected, not a "container" term.
+ */
+function forum_validate($node) {
+  if ($node->taxonomy) {
+    // Extract the node's proper topic ID.
+    $vocabulary = variable_get('forum_nav_vocabulary', '');
+    $containers = variable_get('forum_containers', array());
+    foreach ($node->taxonomy as $term) {
+      if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) {
+        if (in_array($term, $containers)) {
+          $term = taxonomy_get_term($term);
+          form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
+        }
+      }
+    }
+  }
 }
 
-function forum_node_submit($form_id, $node) {
-  $node = (object)$node;
-  if ($node->is_new) {
+/**
+ * Implementation of hook_update().
+ */
+function forum_update($node) {
+  if ($node->revision) {
     db_query("INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)", $node->nid, $node->vid, $node->tid);
   }
   else {
@@ -424,8 +422,18 @@ function forum_prepare(&$node) {
   }
 }
 
-function forum_node_delete_confirm_submit($form_id, $form_values) {
-  db_query('DELETE FROM {forum} WHERE nid = %d', $form_values['node']->nid);
+/**
+ * Implementation of hook_insert().
+ */
+function forum_insert($node) {
+  db_query('INSERT INTO {forum} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $node->tid);
+}
+
+/**
+ * Implementation of hook_delete().
+ */
+function forum_delete(&$node) {
+  db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
 }
 
 /**
diff --git a/modules/menu/menu.module b/modules/menu/menu.module
index 151b86f2855c02f63e0a4fcb2c594b4a5d2bed14..38471093438d8fce5df8f4eea1306582e600dd48 100644
--- a/modules/menu/menu.module
+++ b/modules/menu/menu.module
@@ -146,20 +146,31 @@ function menu_block($op = 'list', $delta = 0) {
   }
 }
 
-function menu_node_delete_confirm_submit($form_id, $form_values) {
-  menu_node_form_delete($form_values['node']);
-  menu_rebuild();
-}
+/**
+ * Implementation of hook_nodeapi().
+ */
+function menu_nodeapi(&$node, $op) {
+
+  if (user_access('administer menu')) {
+    switch ($op) {
+      case 'insert':
+      case 'update':
+        if ($node->menu['delete']) {
+          menu_node_form_delete($node);
+          menu_rebuild();
+        }
+        elseif ($node->menu['title']) {
+          $node->menu['path'] = ($node->menu['path']) ? $node->menu['path'] : "node/$node->nid";
+          menu_edit_item_save($node->menu);
+          menu_rebuild();
+        }
+        break;
 
-function menu_node_submit($form_id, $node) {
-  if ($node->menu['delete']) {
-    menu_node_form_delete($node);
-    menu_rebuild();
-  }
-  elseif ($node->menu['title']) {
-    $node->menu['path'] = ($node->menu['path']) ? $node->menu['path'] : "node/$node->nid";
-    menu_edit_item_save($node->menu);
-    menu_rebuild();
+      case 'delete':
+        menu_node_form_delete($node);
+        menu_rebuild();
+        break;
+    }
   }
 }
 
@@ -187,8 +198,6 @@ function menu_form_alter($form_id, &$form) {
       }
     }
 
-    $form['#submit']['menu_node_submit'] = array();
-
     $form['menu'] = array('#type' => 'fieldset',
       '#title' => t('Menu settings'),
       '#access' => user_access('administer menu'),
@@ -249,10 +258,6 @@ function menu_form_alter($form_id, &$form) {
       );
     }
   }
-
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['menu_node_delete_confirm_submit'] = array();
-  }
 }
 
 /**
diff --git a/modules/node/node.module b/modules/node/node.module
index cd8aec031c4ce3e771f759c8192b63df8e87dcd9..54e6b2c00b115c5da26f8a4fea363eb87ef82cdd 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -600,6 +600,16 @@ function node_save(&$node) {
   db_query($node_query, $node_table_values);
   db_query($revisions_query, $revisions_table_values);
 
+  // Call the node specific callback (if any):
+  if ($node->is_new) {
+    node_invoke($node, 'insert');
+    node_invoke_nodeapi($node, 'insert');
+  }
+  else {
+    node_invoke($node, 'update');
+    node_invoke_nodeapi($node, 'update');
+  }
+
   // Update the node access table for this node.
   node_access_acquire_grants($node);
 
@@ -1061,8 +1071,6 @@ function node_menu($may_cache) {
           'path' => 'node/add/'. $type_url_str,
           'title' => t($name),
           'access' => node_access('create', $type->type),
-          'callback' => 'drupal_get_form',
-          'callback arguments' => array($type->type. '_node_form', array('type' => $type->type))
         );
       }
     }
@@ -1091,7 +1099,7 @@ function node_menu($may_cache) {
           'type' => MENU_LOCAL_TASK);
         $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'),
           'callback' => 'drupal_get_form',
-          'callback arguments' => array('node_delete_confirm', $node),
+          'callback arguments' => array('node_delete_confirm'),
           'access' => node_access('delete', $node),
           'weight' => 1,
           'type' => MENU_CALLBACK);
@@ -1748,9 +1756,53 @@ function node_feed($nodes = 0, $channel = array()) {
 }
 
 /**
- * A form validate handler. Perform checks on the given node.
+ * Prepare node for save and allow modules to make changes.
+ */
+function node_submit($node) {
+  global $user;
+
+  // Convert the node to an object, if necessary.
+  $node = (object)$node;
+
+  // Auto-generate the teaser, but only if it hasn't been set (e.g. by a
+  // module-provided 'teaser' form item).
+  if (!isset($node->teaser)) {
+    $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : '';
+  }
+
+  $access = user_access('administer nodes');
+  if ($access) {
+    // Populate the "authored by" field.
+    if ($account = user_load(array('name' => $node->name))) {
+      $node->uid = $account->uid;
+    }
+    else {
+      $node->uid = 0;
+    }
+
+    $node->created = $node->date ? strtotime($node->date) : NULL;
+  }
+  // Force defaults in case people modify the form:
+  $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
+  foreach (array('status', 'promote', 'sticky', 'revision') as $key) {
+    if (!$access || !isset($node->$key)) {
+      $node->$key = in_array($key, $node_options);
+    }
+  }
+
+  // Do node-type-specific validation checks.
+  node_invoke($node, 'submit');
+  node_invoke_nodeapi($node, 'submit');
+
+  $node->validated = TRUE;
+
+  return $node;
+}
+
+/**
+ * Perform validation checks on the given node.
  */
-function node_form_validate($form_id, $node) {
+function node_validate($node, $form = array()) {
   // Convert the node to an object, if necessary.
   $node = (object)$node;
   $type = node_get_types('type', $node);
@@ -1779,6 +1831,14 @@ function node_form_validate($form_id, $node) {
       form_set_error('date', t('You have to specify a valid date.'));
     }
   }
+
+  // Do node-type-specific validation checks.
+  node_invoke($node, 'validate', $form);
+  node_invoke_nodeapi($node, 'validate', $form);
+}
+
+function node_form_validate($form_id, $form_values, $form) {
+  node_validate($form_values, $form);
 }
 
 function node_object_prepare(&$node) {
@@ -1825,13 +1885,14 @@ function node_form($node, $form_values = NULL) {
     $form['title']['#weight'] = -5;
   }
 
-  // Populate $node so we can assign it to $form['#node].
   $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
   // If this is a new node, fill in the default values.
   if (!isset($node->nid)) {
     foreach (array('status', 'promote', 'sticky', 'revision') as $key) {
       $node->$key = in_array($key, $node_options);
     }
+    global $user;
+    $node->uid = $user->uid;
   }
   else {
     // Nodes being edited should always be preset with the default revision setting.
@@ -1839,17 +1900,39 @@ function node_form($node, $form_values = NULL) {
   }
   $form['#node'] = $node;
 
-  // Node author information is editable only by administrators.
-  $form['author'] = array('#type' => 'fieldset', '#title' => t('Authoring information'), '#access' => user_access('administer nodes'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 20);
-  $form['author']['name'] = array('#type' => 'textfield', '#title' => t('Authored by'), '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $node->nid ? $node->name : $user->name, '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', 'Anonymous'))));
-  $form['author']['date'] = array('#type' => 'textfield', '#title' => t('Authored on'), '#maxlength' => 25, '#default_value' => $node->date, '#description' => t('Format: %time. Leave blank to use the time of form submission.', array('%time' => $node->date)));
+  // Node author information for administrators
+  $form['author'] = array(
+    '#type' => 'fieldset',
+    '#access' => user_access('administer nodes'),
+    '#title' => t('Authoring information'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => 20,
+  );
+  $form['author']['name'] = array('#type' => 'textfield', '#title' => t('Authored by'), '#maxlength' => 60, '#autocomplete_path' => 'user/autocomplete', '#default_value' => $node->name ? $node->name : '', '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', 'Anonymous'))));
+  $form['author']['date'] = array('#type' => 'textfield', '#title' => t('Authored on'), '#maxlength' => 25, '#description' => t('Format: %time. Leave blank to use the time of form submission.', array('%time' => $node->date)));
+
+  if (isset($node->nid)) {
+    $form['author']['date']['#default_value'] = $node->date;
+  }
 
-  // Node options are editable only by administrators.
-  $form['options'] = array('#type' => 'fieldset', '#title' => t('Publishing options'), '#access' => user_access('administer nodes'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 25);
+  // Node options for administrators
+  $form['options'] = array(
+    '#type' => 'fieldset',
+    '#access' => user_access('administer nodes'),
+    '#title' => t('Publishing options'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => 25,
+  );
   $form['options']['status']   = array('#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status);
   $form['options']['promote']  = array('#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote);
   $form['options']['sticky']   = array('#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky);
   $form['options']['revision'] = array('#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision);
+  // These values are used when the user has no administrator accesss.
+  foreach (array('uid', 'created') as $key) {
+    $form[$key] = array('#type' => 'value', '#value' => $node->$key);
+  }
 
   // Add the buttons.
   $form['preview'] = array('#type' => 'button', '#value' => t('Preview'), '#weight' => 40);
@@ -1858,15 +1941,6 @@ function node_form($node, $form_values = NULL) {
     $form['delete'] = array('#type' => 'button', '#value' => t('Delete'), '#weight' => 50);
   }
   $form['#after_build'] = array('node_form_add_preview');
-  $form['#validate'] = array(
-    'node_form_validate' => array(),
-    $node->type .'_node_validate' => array(),
-  );
-  $form['#submit'] = array(
-    $node->type .'_node_submit_early' => array(),
-    'node_form_submit' => array(),
-    $node->type .'_node_submit' => array(),
-  );
   $form['#base'] = 'node_form';
   return $form;
 }
@@ -1930,30 +2004,40 @@ function theme_node_form($form) {
 }
 
 /**
- * Present set of list of node creation links
+ * Present a node submission form or a set of links to such forms.
  */
-function node_add() {
+function node_add($type = NULL) {
   global $user;
 
   $types = node_get_types();
   $type = isset($type) ? str_replace('-', '_', $type) : NULL;
+  // If a node type has been specified, validate its existence.
+  if (isset($types[$type]) && node_access('create', $type)) {
+    // Initialize settings:
+    $node = array('uid' => $user->uid, 'name' => $user->name, 'type' => $type);
 
-  foreach ($types as $type) {
-    if (function_exists($type->module .'_form') && node_access('create', $type->type)) {
-      $type_url_str = str_replace('_', '-', $type->type);
-      $title = t('Add a new @s.', array('@s' => $type->name));
-      $out = '<dt>'. l($type->name, "node/add/$type_url_str", array('title' => $title)) .'</dt>';
-      $out .= '<dd>'. filter_xss_admin($type->description) .'</dd>';
-      $item[$type->type] = $out;
-    }
-  }
-
-  if (isset($item)) {
-    uksort($item, 'strnatcasecmp');
-    $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>';
+    $output = drupal_get_form($type .'_node_form', $node);
+    drupal_set_title(t('Submit @name', array('@name' => $types[$type]->name)));
   }
   else {
-    $output = t('No content types available.');
+    // If no (valid) node type has been provided, display a node type overview.
+    foreach ($types as $type) {
+      if (function_exists($type->module .'_form') && node_access('create', $type->type)) {
+        $type_url_str = str_replace('_', '-', $type->type);
+        $title = t('Add a new @s.', array('@s' => $type->name));
+        $out = '<dt>'. l($type->name, "node/add/$type_url_str", array('title' => $title)) .'</dt>';
+        $out .= '<dd>'. filter_xss_admin($type->description) .'</dd>';
+        $item[$type->type] = $out;
+      }
+    }
+
+    if (isset($item)) {
+      uksort($item, 'strnatcasecmp');
+      $output = t('Choose the appropriate item from the list:') .'<dl>'. implode('', $item) .'</dl>';
+    }
+    else {
+      $output = t('No content types available.');
+    }
   }
 
   return $output;
@@ -2033,27 +2117,31 @@ function theme_node_log_message($log) {
   return '<div class="log"><div class="title">'. t('Log') .':</div>'. $log .'</div>';
 }
 
-function node_form_submit($form_id, &$node) {
+function node_form_submit($form_id, $edit) {
   global $user;
 
-  $node = (object)$node;
-  // Auto-generate the teaser, but only if it hasn't been set (e.g. by a
-  // module-provided 'teaser' form item).
-  if (!isset($node->teaser)) {
-    $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : '';
-  }
-
-  // Populate the uid based on name field.
-  $account = user_load(array('name' => $node->name));
-  $node->uid = $account->uid;
-  $node->created = strtotime($node->date);
+  // Fix up the node when required:
+  $node = node_submit($edit);
 
-  $action = $node->nid ? 'updated' : 'created';
-  node_save($node);
-  watchdog('content', t("@type: $action %title.", array('@type' => t($node->type), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
-  drupal_set_message(t("The %post was $action.", array ('%post' => node_get_types('name', $node))));
-
-  // redirect the submitter as needed
+  // Prepare the node's body:
+  if ($node->nid) {
+    // Check whether the current user has the proper access rights to
+    // perform this operation:
+    if (node_access('update', $node)) {
+      node_save($node);
+      watchdog('content', t('@type: updated %title.', array('@type' => t($node->type), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));
+      drupal_set_message(t('The %post was updated.', array ('%post' => node_get_types('name', $node))));
+    }
+  }
+  else {
+    // Check whether the current user has the proper access rights to
+    // perform this operation:
+    if (node_access('create', $node)) {
+      node_save($node);
+      watchdog('content', t('@type: added %title.', array('@type' => t($node->type), '%title' => $node->title)), WATCHDOG_NOTICE, l(t('view'), "node/$node->nid"));
+      drupal_set_message(t('Your %post was created.', array ('%post' => node_get_types('name', $node))));
+    }
+  }
   if ($node->nid) {
     if (node_access('view', $node)) {
       return 'node/'. $node->nid;
@@ -2069,9 +2157,13 @@ function node_form_submit($form_id, &$node) {
 /**
  * Menu callback -- ask for confirmation of node deletion
  */
-function node_delete_confirm($node) {
+function node_delete_confirm() {
+  $edit = $_POST;
+  $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
+  $node = node_load($edit['nid']);
+
   if (node_access('delete', $node)) {
-    $form['node'] = array('#type' => 'value', '#value' => $node);
+    $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
     $output = confirm_form($form,
                    t('Are you sure you want to delete %title?', array('%title' => $node->title)),
                    $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action cannot be undone.'),
@@ -2085,7 +2177,10 @@ function node_delete_confirm($node) {
  * Execute node deletion
  */
 function node_delete_confirm_submit($form_id, $form_values) {
-  node_delete($form_values['node']->nid);
+  if ($form_values['confirm']) {
+    node_delete($form_values['nid']);
+  }
+
   return '';
 }
 
@@ -2100,6 +2195,10 @@ function node_delete($nid) {
     db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
     db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
 
+    // Call the node-specific callback (if any):
+    node_invoke($node, 'delete');
+    node_invoke_nodeapi($node, 'delete');
+
     // Clear the cache so an anonymous poster can see the node being deleted.
     cache_clear_all();
 
diff --git a/modules/path/path.module b/modules/path/path.module
index 5b9fdbf43aa94f698469ce09b13420f8a943fe88..6e08057d47902b352d96de9e0ee86c071ba5f23b 100644
--- a/modules/path/path.module
+++ b/modules/path/path.module
@@ -207,6 +207,16 @@ function path_form($edit = '') {
 function path_nodeapi(&$node, $op, $arg) {
   if (user_access('create url aliases') || user_access('administer url aliases')) {
     switch ($op) {
+      case 'validate':
+        $node->path = trim($node->path);
+        if ($node->path && !valid_url($node->path)) {
+          form_set_error('path', t('The path is invalid.'));
+        }
+        else if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s'", $node->path, "node/$node->nid"))) {
+          form_set_error('path', t('The path is already in use.'));
+        }
+        break;
+
       case 'load':
         $path = "node/$node->nid";
         // We don't use drupal_get_path_alias() to avoid custom rewrite functions.
@@ -216,36 +226,26 @@ function path_nodeapi(&$node, $op, $arg) {
           $node->path = db_result($result);
         }
         break;
-    }
-  }
-}
 
-function path_node_delete_confirm_submit($form_id, $form_values) {
-  $path = 'node/'. $form_values['node']->nid;
-  if (drupal_get_path_alias($path) != $path) {
-    path_set_alias($path);
-  }
-}
+      case 'insert':
+        // Don't try to insert if path is NULL. We may have already set
+        // the alias ahead of time.
+        if ($node->path) {
+          path_set_alias("node/$node->nid", $node->path);
+        }
+        break;
 
-function path_node_submit($form_id, $node) {
-  if ($node->is_new) {
-    // Don't try to insert if path is NULL. We may have already set the alias ahead of time.
-    if ($node->path) {
-      path_set_alias("node/$node->nid", $node->path);
-    }
-  }
-  else {
-    path_set_alias("node/$node->nid", $node->path, $node->pid);
-  }
-}
+      case 'update':
+        path_set_alias("node/$node->nid", $node->path, $node->pid);
+        break;
 
-function path_node_validate($form_id, $node) {
-  $node['path'] = trim($node['path']);
-  if ($node['path'] && !valid_url($node['path'])) {
-    form_set_error('path', t('The path is invalid.'));
-  }
-  else if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s'", $node['path'], 'node/'. $node['nid']))) {
-    form_set_error('path', t('The path is already in use.'));
+      case 'delete':
+        $path = "node/$node->nid";
+        if (drupal_get_path_alias($path) != $path) {
+          path_set_alias($path);
+        }
+        break;
+    }
   }
 }
 
@@ -254,12 +254,6 @@ function path_node_validate($form_id, $node) {
  */
 function path_form_alter($form_id, &$form) {
   if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
-
-    if (user_access('create url aliases')) {
-      $form['#validate']['path_node_validate'] = array();
-      $form['#submit']['path_node_submit'] = array();
-    }
-
     $path = $form['#node']->path;
     $form['path'] = array(
       '#type' => 'fieldset',
@@ -284,10 +278,6 @@ function path_form_alter($form_id, &$form) {
       );
     }
   }
-
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['path_node_delete_confirm_submit'] = array();
-  }
 }
 
 
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index 16f76bb9a17d071da165c4b3d227949620450137..ad0728c950ad0ffe8e89f632ea52699cbf6fe696 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -76,45 +76,45 @@ function poll_cron() {
   }
 }
 
-function poll_node_delete_confirm_submit($form_id, $form_values) {
-  db_query("DELETE FROM {poll} WHERE nid = %d", $form_values['node']->nid);
-  db_query("DELETE FROM {poll_choices} WHERE nid = %d", $form_values['node']->nid);
-  db_query("DELETE FROM {poll_votes} WHERE nid = %d", $form_values['node']->nid);
+/**
+ * Implementation of hook_delete().
+ */
+function poll_delete($node) {
+  db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
 }
 
 /**
- * An early submit handler of the poll_node_form. Runs before the node is saved.
+ * Implementation of hook_submit().
  */
-function poll_node_submit_early($form_id, &$node) {
-  $node = (object)$node;
+function poll_submit(&$node) {
   // Renumber fields
   $node->choice = array_values($node->choice);
   $node->teaser = poll_teaser($node);
 }
 
-function poll_node_validate($form_id, $node) {
-  $node = (object)$node;
-  // Check for at least two options and validate amount of votes:
-  $realchoices = 0;
-  // Renumber fields
-  $node->choice = array_values($node->choice);
-  foreach ($node->choice as $i => $choice) {
-    if ($choice['chtext'] != '') {
-      $realchoices++;
-    }
-    if ($choice['chvotes'] < 0) {
-      form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
+/**
+ * Implementation of hook_validate().
+ */
+function poll_validate($node) {
+  if (isset($node->title)) {
+    // Check for at least two options and validate amount of votes:
+    $realchoices = 0;
+    // Renumber fields
+    $node->choice = array_values($node->choice);
+    foreach ($node->choice as $i => $choice) {
+      if ($choice['chtext'] != '') {
+        $realchoices++;
+      }
+      if ($choice['chvotes'] < 0) {
+        form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
+      }
     }
-  }
-
-  if ($realchoices < 2) {
-    form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
-  }
-}
 
-function poll_form_alter($form_id, &$form) {
-  if ($form_id == 'node_delete_confirm' && $form['node']['#value']->type == 'poll') {
-    $form['#submit']['poll_node_delete_confirm_submit'] = array();
+    if ($realchoices < 2) {
+      form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
+    }
   }
 }
 
@@ -634,16 +634,9 @@ function poll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
   return $node;
 }
 
-function poll_node_submit($form_id, $node) {
-  $node = (object)$node;
-  if ($node->is_new) {
-    poll_insert($node);
-  }
-  else {
-    poll_update($node);
-  }
-}
-
+/**
+ * Implementation of hook_update().
+ */
 function poll_update($node) {
   db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
 
diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module
index 757ddae95817693f250595fa6c2a3f77aa6a26d6..6ae63436d584c77d9cb92c844800df9bfaa1b7ef 100644
--- a/modules/statistics/statistics.module
+++ b/modules/statistics/statistics.module
@@ -531,13 +531,14 @@ function _statistics_format_item($title, $path) {
   return $output;
 }
 
-function statistics_node_delete_confirm_submit($form_id, $form_values) {
-  db_query('DELETE FROM {node_counter} WHERE nid = %d', $form_values['node']->nid);
-}
-
-function statistics_form_alter($form_id, &$form) {
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['statistics_node_delete_confirm_submit'] = array();
+/**
+ * Implementation of hook_nodeapi().
+ */
+function statistics_nodeapi(&$node, $op, $arg = 0) {
+  switch ($op) {
+    case 'delete':
+      // clean up statistics table when node is deleted
+      db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
   }
 }
 
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index 82fd2bbea0a21584fc154dd0adc632984c0451e0..bece3b163f9dad11701af7570c612cd20eb526d1 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -606,10 +606,6 @@ function taxonomy_get_vocabularies($type = NULL) {
  */
 function taxonomy_form_alter($form_id, &$form) {
   if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
-    // inject custom validate and submit handlers
-    $form['#validate']['taxonomy_node_validate'] = array();
-    $form['#submit']['taxonomy_node_submit'] = array();
-
     $node = $form['#node'];
 
     if (!isset($node->taxonomy)) {
@@ -676,10 +672,6 @@ function taxonomy_form_alter($form_id, &$form) {
       $form['taxonomy'] += array('#type' => 'fieldset', '#title' => t('Categories'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, '#weight' => -3);
     }
   }
-
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['taxonomy_node_delete_confirm_submit'] = array();
-  }
 }
 
 /**
@@ -713,9 +705,9 @@ function taxonomy_node_get_terms($nid, $key = 'tid') {
 /**
  * Make sure incoming vids are free tagging enabled.
  */
-function taxonomy_node_validate($form_id, $node) {
-  if ($node['taxonomy']) {
-    $terms = $node['taxonomy'];
+function taxonomy_node_validate(&$node) {
+  if ($node->taxonomy) {
+    $terms = $node->taxonomy;
     if ($terms['tags']) {
       foreach ($terms['tags'] as $vid => $vid_value) {
         $vocabulary = taxonomy_get_vocabulary($vid);
@@ -732,14 +724,14 @@ function taxonomy_node_validate($form_id, $node) {
 /**
  * Save term associations for a given node.
  */
-function taxonomy_node_submit($form_id, $node) {
-  taxonomy_node_delete($node->nid);
+function taxonomy_node_save($nid, $terms) {
+  taxonomy_node_delete($nid);
 
   // Free tagging vocabularies do not send their tids in the form,
   // so we'll detect them here and process them independently.
-  if (isset($node->taxonomy['tags'])) {
-    $typed_input = $node->taxonomy['tags'];
-    unset($node->taxonomy['tags']);
+  if (isset($terms['tags'])) {
+    $typed_input = $terms['tags'];
+    unset($terms['tags']);
 
     foreach ($typed_input as $vid => $vid_value) {
       // This regexp allows the following types of user input:
@@ -775,27 +767,27 @@ function taxonomy_node_submit($form_id, $node) {
 
         // Defend against duplicate, different cased tags
         if (!isset($inserted[$typed_term_tid])) {
-          db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $node->nid, $typed_term_tid);
+          db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $typed_term_tid);
           $inserted[$typed_term_tid] = TRUE;
         }
       }
     }
   }
 
-  if (is_array($node->taxonomy)) {
-    foreach ($node->taxonomy as $term) {
+  if (is_array($terms)) {
+    foreach ($terms as $term) {
       if (is_array($term)) {
         foreach ($term as $tid) {
           if ($tid) {
-            db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $node->nid, $tid);
+            db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $tid);
           }
         }
       }
       else if (is_object($term)) {
-        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $node->nid, $term->tid);
+        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term->tid);
       }
       else if ($term) {
-        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $node->nid, $term);
+        db_query('INSERT INTO {term_node} (nid, tid) VALUES (%d, %d)', $nid, $term);
       }
     }
   }
@@ -1165,6 +1157,18 @@ function taxonomy_nodeapi($node, $op, $arg = 0) {
     case 'load':
      $output['taxonomy'] = taxonomy_node_get_terms($node->nid);
      return $output;
+    case 'insert':
+      taxonomy_node_save($node->nid, $node->taxonomy);
+      break;
+    case 'update':
+      taxonomy_node_save($node->nid, $node->taxonomy);
+      break;
+    case 'delete':
+      taxonomy_node_delete($node->nid);
+      break;
+    case 'validate':
+      taxonomy_node_validate($node);
+      break;
     case 'rss item':
       return taxonomy_rss_item($node);
     case 'update index':
@@ -1172,10 +1176,6 @@ function taxonomy_nodeapi($node, $op, $arg = 0) {
   }
 }
 
-function taxonomy_node_confirm_delete_submit($form_id, $form_values) {
-  taxonomy_node_delete($form_values['node']->nid);
-}
-
 /**
  * Implementation of hook_nodeapi('update_index').
  */
diff --git a/modules/upload/upload.module b/modules/upload/upload.module
index d4c2ad5a510f5f65a49c1f85e7f55552b2dc5972..26a46e74b56485f8d70ab801dc2f16c67d8138dd 100644
--- a/modules/upload/upload.module
+++ b/modules/upload/upload.module
@@ -361,13 +361,7 @@ function upload_form_alter($form_id, &$form) {
 
   if (isset($form['type'])) {
     $node = $form['#node'];
-
     if ($form['type']['#value'] .'_node_form' == $form_id && variable_get("upload_$node->type", TRUE)) {
-      if (user_access('upload files')) {
-        $form['#validate']['upload_node_validate'] = array();
-        $form['#submit']['upload_node_submit'] = array();
-      }
-
       drupal_add_js('misc/progress.js');
       drupal_add_js('misc/upload.js');
 
@@ -393,14 +387,9 @@ function upload_form_alter($form_id, &$form) {
       $form['#attributes']['enctype'] = 'multipart/form-data';
     }
   }
-
-  if ($form_id == 'node_delete_confirm') {
-    $form['#submit']['upload_node_delete_confirm_submit'] = array();
-  }
 }
 
-function upload_node_validate($form_id, $node) {
-  $node = (object)$node;
+function _upload_validate(&$node) {
   // Accumulator for disk space quotas.
   $filesize = 0;
 
@@ -492,6 +481,10 @@ function upload_nodeapi(&$node, $op, $teaser) {
       _upload_prepare($node);
       break;
 
+    case 'validate':
+      _upload_validate($node);
+      break;
+
     case 'view':
       if (isset($node->files) && user_access('view uploaded files')) {
         // Add the attachments list to node body with a heavy
@@ -526,6 +519,18 @@ function upload_nodeapi(&$node, $op, $teaser) {
         }
       }
       break;
+
+    case 'insert':
+    case 'update':
+      if (user_access('upload files')) {
+        upload_save($node);
+      }
+      break;
+
+    case 'delete':
+      upload_delete($node);
+      break;
+
     case 'delete revision':
       upload_delete_revision($node);
       break;
@@ -572,12 +577,6 @@ function upload_fix_preview_urls($elements, &$content) {
   }
 }
 
-function upload_node_submit($form_id, $node) {
-  if (user_access('upload files')) {
-    upload_save($node);
-  }
-}
-
 /**
  * Displays file attachments in table
  */
@@ -729,9 +728,9 @@ function upload_save($node) {
   }
 }
 
-function upload_node_delete_confirm_submit($form_id, $form_values) {
+function upload_delete($node) {
   $files = array();
-  $result = db_query('SELECT * FROM {files} WHERE nid = %d', $form_values['node']->nid);
+  $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid);
   while ($file = db_fetch_object($result)) {
     $files[$file->fid] = $file;
   }
@@ -743,7 +742,7 @@ function upload_node_delete_confirm_submit($form_id, $form_values) {
   }
 
   // Delete all files associated with the node
-  db_query('DELETE FROM {files} WHERE nid = %d', $form_values['node']->nid);
+  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
 }
 
 function upload_delete_revision($node) {
@@ -883,7 +882,7 @@ function upload_js() {
 
   // Handle new uploads, and merge tmp files into node-files.
   _upload_prepare($node);
-  upload_node_validate(NULL, $node);
+  _upload_validate($node);
 
   $form = _upload_form($node);
   foreach (module_implements('form_alter') as $module) {