=== modified file 'includes/common.inc'
--- includes/common.inc	2007-03-09 16:58:54 +0000
+++ includes/common.inc	2007-03-13 16:58:42 +0000
@@ -2295,3 +2295,277 @@ function int2vancode($i = 0) {
   $length = strlen($num);
   return chr($length + ord('0') - 1) . $num;
 }
+
+/**
+ * Create/build/execute deletion packages.
+ *
+ * @param $id
+ *   A unique string identifier for the deletion being performed.
+ *
+ * @param $id
+ *   An operation to be performed -- one of the following values:
+ *
+ *      'query'       -- Pass a deletion query to the API.
+ *
+ *      'new package' -- Used to begin a new deletion package. A package is set of deletion
+ *                       queries associated with a particular kind of deletion -- for example,
+ *                       all the queries associated with a node deletion. Most often it will
+ *                       not be necessary to start a new package, as most non-core deletions will
+ *                       already be part of a package initiated by core. Once a package has
+ *                       been started, all calls to drupal_delete() containing the 'query',
+ *                       'metadata', and 'callback' operations are added to the package. A
+ *                       package is complete when either a new package is started, or
+ *                       when drupal_delete() is called with the 'confirm' or 'execute'
+ *                       operations.
+ *
+ *      'confirm'     -- Initiates the confirmation cycle.  This command fully builds all packages
+ *                       for deletion, and returns a confirm form array containing any injected messages
+ *                       which can be used to print a confirmation screen.
+ *
+ *      'execute'     -- Initiates the deletion of all constructed packages.  Confirmation messages
+ *                       are bypassed, but abort messages are respected.
+ *
+ *      'callback'    -- Register a callback function. The function is called after the package
+ *                       has been deleted. Useful for miscellaneous cleanup, user messages, etc.
+ *
+ *      'metadata'    -- Pass metadata related to the deletion or package to the API.  This is made
+ *                       available to all hooks called during the deletion cycle.
+ *
+ *      'delete'      -- Forces the deletion to be performed immediately and separately from
+ *                       any currently building package. No confirm or abort messages are built
+ *                       or consulted.  Note that the 'query' operation should almost always be
+ *                       be used instead, as it exposes the query to the confirm/abort functionality
+ *                       of the API. Use of this operation should be reserved for deletions that are
+ *                       unrelated to the main package contents, such as clearing of a cache table,
+ *                       or wiping of related search data.
+ *
+ * Additional arguments are passed to the function, depending on the operation to be
+ * performed:
+ *
+ *      'query'       -- The query to be passed, followed by any additional arguments for escaped
+ *                       values.
+ *                       ex. 'DELETE FROM {comments} WHERE nid = %d', array('nid' => $node->nid)
+ *                       The additional arguments can be passed in any fashion that db_query() accepts.
+ *
+ *      'new package' -- No arguments required.
+ *
+ *      'confirm'     -- An associative array with the following key/value pairs:
+ *        'form'      => Optional. An array representing the form elements to pass to the confirm form.
+ *        'question'  => Optional. The question for the confirm form.
+ *        'path'      => Optional. The cancellation path for the confirm form.
+ *
+ *        Also, any valid options from the $options argument of confirm_form() may be passed,
+ *        and they will be passed through to the confirm form.
+ *
+ *      'execute'     -- No arguments required.
+ *
+ *      'callback'    -- An associative array of callback functions, key = name of function,
+ *                       value = an array of arguments to pass to the function.
+ *
+ *      'metadata'    -- An associative array of metadata.
+ *
+ *      'delete'      -- The query to be executed, followed by any additional arguments for escaped
+ *                       values.
+ *                       ex. 'DELETE FROM {comments} WHERE nid = %d', array('nid' => $node->nid)
+ *
+ * Note that if the 'query' operation is invoked without being part of a package, then the query
+ * executes as if the 'delete' operation were invoked.
+ */
+function drupal_delete($id, $op) {
+  static $delete_id = 0;
+  static $is_package = NULL;
+  static $deletion_data = array();
+  static $abort = NULL;
+
+  $confirm = NULL;
+  $execute = NULL;
+  $destination = FALSE;
+
+  // Process additional arguments.
+  $all_args = func_get_args();
+  if (count($all_args) > 2) {
+    $args = array_slice($all_args, 2);
+    // Shift off query string if present.
+    if ($op == 'query' || $op == 'delete') {
+      $query = array_shift($args);
+    }
+    // Clean up args passed as an array.
+    if (is_array($args[0])) {
+      $args = $args[0];
+    }
+  }
+  else {
+    $args = array();
+  }
+
+  switch ($op) {
+    // Internal operation used to set the proper package ID before passing
+    // package contents to any hooks.
+    case 'set package':
+      $delete_id = $args[0];
+      return;
+    // Query to add to a package
+    case 'query':
+      // Special case for single deletions.
+      if (!isset($is_package)) {
+        db_query($query, $args);
+        return;
+      }
+      // Add the information for this deletion into the package array.
+      else {
+        $deletion_data[$delete_id][$id][] = array('query' => $query, 'args' => $args);
+      }
+      break;
+    // New package -- mark it as such and get the next deletion ID.
+    case 'new package':
+      $delete_id++;
+      $is_package = TRUE;
+      $deletion_data[$delete_id]['metadata'] = array();
+      $deletion_data[$delete_id]['callbacks'] = array();
+      break;
+    // Confirm all packages.
+    case 'confirm':
+      // Set execute switch and destination if bypass is enabled.
+      if (variable_get('bypass_delete_confirmation', 0)) {
+        $execute = TRUE;
+        $destination = isset($args['destination']) ? $args['destination'] : NULL;
+      }
+      else {
+        $confirm = TRUE;
+      }
+      break;
+    // Execute all packages.
+    case 'execute':
+      $execute = TRUE;
+      break;
+    // Add callback.
+    case 'callback':
+      $deletion_data[$delete_id]['callbacks'] += $args;
+      break;
+    // Add metadata.
+    case 'metadata':
+      $deletion_data[$delete_id]['metadata'] += $args;
+      break;
+    // Special case for single deletions.
+    case 'delete':
+      db_query($query, $args);
+      return;
+    default:
+      drupal_set_message(t('Illegal deletion operation %op', array('%op' => $op)), 'error');
+      return;
+  }
+
+  // Allow modules to inject confirm/abort data.
+  if (isset($confirm) || isset($execute)) {
+    // $abort is kept as a static and checked to avoid calling this code twice
+    // when the package is executed.
+    if (!isset($abort)) {
+      $abort = array();
+      $form = isset($args['form']) ? $args['form'] : array();
+
+      foreach ($deletion_data as $package_id => $package_info) {
+        // Reset $delete_id to the package to be passed.
+        drupal_delete('', 'set package', $package_id);
+        // Call hook_delete_pre() once for each package.
+        foreach (module_implements('delete_pre') as $module) {
+          $function = $module .'_delete_pre';
+          $confirm_data = $function($package_info);
+          // Check for an aborted package.
+          if (isset($confirm_data['abort'])) {
+            $abort[$package_id][] = $confirm_data['abort'];
+            unset($confirm_data['abort']);
+          }
+          // Add elements to confirm form.
+          $form = array_merge_recursive($form, $confirm_data);
+        }
+      }
+      // Generate the confirm form.
+      if (isset($confirm)) {
+        $question = isset($args['question']) ? $args['question'] : t('Delete the item?');
+        $path = isset($args['path']) ? $args['path'] : 'node';
+        unset($args['question'], $args['path']);
+        return confirm_form($form, $question, $path, $args);
+      }
+    }
+  }
+
+  // Execute all constructed packages.
+  if (isset($execute)) {
+    // Process the abort messages for each package--they cancel out deletion of the package.
+    foreach ($abort as $package_id => $messages) {
+      $messages = array_filter($messages);
+      if (count($messages)) {
+        drupal_set_message(theme('item_list', $messages), 'error');
+      }
+      // Remove the package.
+      unset($deletion_data[$package_id]);
+    }
+
+    // Explicitly reset the package information.
+    $delete_id = 0;
+    $is_package = NULL;
+    $package_data = $deletion_data;
+    $deletion_data = array();
+    $abort = NULL;
+
+    // Execute the deletion of all packages.
+    drupal_delete_execute($package_data, $destination);
+  }
+}
+
+/**
+ * Executes all database deletions in all packages of a single page call.
+ *
+ * @param $package_data
+ *   The complete array of deletion data for all packages.
+ * @param $destination
+ *   A destination for operations that bypass the confirm step.
+ */
+function drupal_delete_execute($package_data, $destination = NULL) {
+  foreach ($package_data as $package_id => $package_info) {
+    // Allow modules to perform any operations just prior to the deletion of the packages.
+    module_invoke_all('delete_execute', $package_info);
+    // Extract callbacks and metadata.
+    $callbacks = $package_info['callbacks'];
+    unset($package_info['callbacks'], $package_info['metadata']);
+    // Perform the deletions.
+    foreach ($package_info as $deletions) {
+      foreach ($deletions as $deletion) {
+      	db_query($deletion['query'], $deletion['args']);
+      }
+    }
+    // Execute post-deletion callbacks.
+    foreach ($callbacks as $function => $args) {
+      if (function_exists($function)) {
+        call_user_func_array($function, $args);
+      }
+    }
+  }
+
+  // Redirect for confirmation bypass.
+  drupal_redirect($destination);
+}
+
+/**
+ * Generates a drupal_goto() based on the value of $goto.
+ *
+ * @param $goto
+ *   Can be any of the following values:
+ *      -- A string representing a valid Drupal path.
+ *      -- An array containing arguments to pass to drupal_goto().
+ *      -- NULL to redirect to $_GET['q'].
+ *      -- FALSE to bypass the redirection.
+ */
+function drupal_redirect($goto) {
+  if ($goto !== FALSE) {
+    if (isset($goto)) {
+      if (is_array($goto)) {
+        call_user_func_array('drupal_goto', $goto);
+      }
+      else {
+        drupal_goto($goto);
+      }
+    }
+    drupal_goto($_GET['q']);
+  }
+}

=== modified file 'includes/form.inc'
--- includes/form.inc	2007-03-17 20:38:56 +0000
+++ includes/form.inc	2007-03-20 18:03:39 +0000
@@ -494,17 +494,7 @@ function drupal_redirect_form($form, $re
   if (isset($form['#redirect'])) {
     $goto = $form['#redirect'];
   }
-  if (!isset($goto) || ($goto !== FALSE)) {
-    if (isset($goto)) {
-      if (is_array($goto)) {
-        call_user_func_array('drupal_goto', $goto);
-      }
-      else {
-        drupal_goto($goto);
-      }
-    }
-    drupal_goto($_GET['q']);
-  }
+  drupal_redirect(isset($goto) ? $goto : NULL);
 }
 
 /**

=== modified file 'modules/book/book.module'
--- modules/book/book.module	2007-03-17 20:38:56 +0000
+++ modules/book/book.module	2007-03-20 18:03:39 +0000
@@ -453,10 +453,10 @@ function book_nodeapi(&$node, $op, $teas
       }
       break;
     case 'delete revision':
-      db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
+      drupal_delete('book', 'query', 'DELETE FROM {book} WHERE vid = %d', array('vid' => $node->vid));
       break;
     case 'delete':
-      db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+      drupal_delete('book', 'query', 'DELETE FROM {book} WHERE nid = %d', array('nid' => $node->nid));
       break;
   }
 }

=== modified file 'modules/comment/comment.module'
--- modules/comment/comment.module	2007-03-12 14:42:37 +0000
+++ modules/comment/comment.module	2007-03-22 23:03:53 +0000
@@ -184,7 +184,8 @@ function comment_menu() {
 
   $items['comment/delete'] = array(
     'title' => t('Delete comment'),
-    'page callback' => 'comment_delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('comment_delete', 2),
     'access arguments' => array('administer comments'),
     'type' => MENU_CALLBACK,
   );
@@ -426,8 +427,9 @@ function comment_nodeapi(&$node, $op, $a
       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);
+      $args = array('nid' => $node->nid);
+      drupal_delete('comments', 'query', 'DELETE FROM {comments} WHERE nid = %d', $args);
+      drupal_delete('node_comment_statistics', 'query', 'DELETE FROM {node_comment_statistics} WHERE nid = %d', $args);
       break;
 
     case 'update index':
@@ -453,6 +455,89 @@ function comment_nodeapi(&$node, $op, $a
 }
 
 /**
+ * Implementation of hook_delete_pre().  Called during the building of
+ * the confirmation cycle, allowing modules to inject custom messages into the
+ * confirm screen, or signal cancellation of a package deletion.
+ *
+ * @param $package_info
+ *   An array of deletion information for the package. Metadata is stored in
+ *   $package_info['metadata'], callbacks are stored in $package_info['callbacks'],
+ *   and individual deletion queries are stored in $package_info[$id][deletion_number]
+ *   Each individual deletion is an associative array as follows:
+ *     'query' => The query with before it's escaped values are inserted.
+ *     'args' => The array of arguments to substitute for the escaped values.
+ *
+ * @return
+ *   A form array representing form elements to add to the confirmation form.
+ *   To initiate the cancellation of a package deletion, the special abort syntax
+ *   may be used:
+ *
+ *   $form['abort'] = t('Add a custom abort message here.');
+ */
+function comment_delete_pre($package_info) {
+  // Are nodes being deleted?
+  if (isset($package_info['node'])) {
+    $form = array();
+    $messages = array();
+    // Loop through each node deletion query.
+    foreach ($package_info['node'] as $data) {
+      // Pull the nid.
+    	$nid = $data['args']['nid'];
+    	$title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
+
+    	// Check for comments on the node.
+      if ($count = db_num_rows(db_query('SELECT nid FROM {comments} WHERE nid = %d', $nid))) {
+        // Add a blurb about how many comments will be deleted.
+        // Note that this format works so that the comment messages will
+        // also be neatly placed with the appropriate node title for multiple
+        // node deletions at admin/content/node  :)
+        $form["node_$nid"]["comment"] = array(
+          '#value' => '<div>'. t('!count will be deleted. ', array('!count' => format_plural($count, '1 comment', '@count comments'))) .'</div>',
+        );
+
+        // More than two comments on this node -- prevent deletion.
+        if ($count > 2) {
+          $form['abort'] = t('%title has more than 2 comments attached -- deletion aborted.', array('%title' => $title));
+        }
+        else {
+          // Build some necessary data for the successful deletion messages for comments.
+          $messages[$nid] = array('count' => $count, 'title' => check_plain($title));
+        }
+      }
+    }
+    // If any comment deleted messages exist, pass them in as metadata for this package.
+    if (count($messages)) {
+      drupal_delete('comment', 'metadata', array('comment_messages' => $messages));
+    }
+
+    return $form;
+  }
+}
+
+/**
+ * Implementation of hook_delete_execute().  Called just prior to the deletion
+ * of the package from the database, allowing modules to perform any last minute
+ * operations on the data.
+ *
+ * @param $package_info
+ *   An array of deletion information for the package. Metadata is stored in
+ *   $package_info['metadata'], callbacks are stored in $package_info['callbacks'],
+ *   and individual deletion queries are stored in $package_info[$id][deletion_number]
+ *   Each individual deletion is an associative array as follows:
+ *     'query' => The query with before it's escaped values are inserted.
+ *     'args' => The array of arguments to substitute for the escaped values.
+ */
+function comment_delete_execute($package_info) {
+  // Any comment messages to display?
+  if (isset($package_info['metadata']['comment_messages'])) {
+    // Each message present.
+    foreach ($package_info['metadata']['comment_messages'] as $message) {
+    	drupal_set_message(t('Deleted !count from %title', array('!count' => format_plural($message['count'], '1 comment', '@count comments'), '%title' => $message['title'])));
+    }
+  }
+}
+
+/**
  * Implementation of hook_user().
  *
  * Provides signature customization for the user's comments.
@@ -1078,41 +1163,38 @@ function comment_delete($cid = NULL) {
   $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid));
   $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
 
-  $output = '';
-
-  // We'll only delete if the user has confirmed the
-  // deletion using the form in our else clause below.
-  if (is_object($comment) && is_numeric($comment->cid) && $_POST['confirm']) {
-    drupal_set_message(t('The comment and all its replies have been deleted.'));
+  if (is_object($comment) && is_numeric($comment->cid)) {
+    drupal_delete('comment', 'new package');
+    drupal_delete('comment', 'callback', array('comment_delete_post' => array($comment)));
 
     // Delete comment and its replies.
     _comment_delete_thread($comment);
 
-    _comment_update_node_statistics($comment->nid);
-
-    // Clear the cache so an anonymous user sees that his comment was deleted.
-    cache_clear_all();
+    return drupal_delete('comment', 'confirm',
+      array(
+        'question' => t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)),
+        'path' => 'node/'. $comment->nid,
+        'description' => t('Any replies to this comment will be lost. This action cannot be undone.'),
+        'destination' => 'node/'. $comment->nid,
+      )
+    );
 
-    drupal_goto("node/$comment->nid");
-  }
-  else if (is_object($comment) && is_numeric($comment->cid)) {
-    $output = drupal_get_form('comment_confirm_delete', $comment->subject, $comment->nid);
   }
   else {
-    drupal_set_message(t('The comment no longer exists.'));
+    drupal_set_message(t('The comment no longer exists.'), 'error');
+    drupal_goto('<front>');
   }
-
-  return $output;
 }
 
-function comment_confirm_delete($subject, $nid) {
-  return confirm_form(
-    array(),
-    t('Are you sure you want to delete the comment %title?', array('%title' => $subject)),
-    'node/'. $nid,
-    t('Any replies to this comment will be lost. This action cannot be undone.'),
-    t('Delete'),
-    t('Cancel'));
+function comment_delete_post($comment) {
+
+  drupal_set_message(t('The comment and all its replies have been deleted.'));
+  watchdog('content', t('Comment: deleted %subject and all its replies.', array('%subject' => $comment->subject)));
+
+  _comment_update_node_statistics($comment->nid);
+
+  // Clear the cache so an anonymous user sees that his comment was deleted.
+  cache_clear_all();
 }
 
 /**
@@ -1844,8 +1926,7 @@ function _comment_delete_thread($comment
   }
 
   // Delete the comment:
-  db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
-  watchdog('content', t('Comment: deleted %subject.', array('%subject' => $comment->subject)));
+  drupal_delete('comment', 'query', 'DELETE FROM {comments} WHERE cid = %d', array('cid' => $comment->cid));
 
   comment_invoke_comment($comment, 'delete');
 

=== modified file 'modules/forum/forum.module'
--- modules/forum/forum.module	2007-03-17 20:38:56 +0000
+++ modules/forum/forum.module	2007-03-20 18:03:40 +0000
@@ -146,7 +146,7 @@ function forum_perm() {
 function forum_nodeapi(&$node, $op, $teaser, $page) {
   switch ($op) {
     case 'delete revision':
-      db_query('DELETE FROM {forum} WHERE vid = %d', $node->vid);
+      drupal_delete('forum', 'query', 'DELETE FROM {forum} WHERE vid = %d', array('vid' => $node->vid));
       break;
   }
 }
@@ -430,7 +430,7 @@ function forum_insert($node) {
  * Implementation of hook_delete().
  */
 function forum_delete(&$node) {
-  db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
+  drupal_delete('forum', 'query', 'DELETE FROM {forum} WHERE nid = %d', array('nid' => $node->nid));
 }
 
 /**

=== modified file 'modules/menu/menu.module'
--- modules/menu/menu.module	2007-03-17 20:38:56 +0000
+++ modules/menu/menu.module	2007-03-20 18:03:39 +0000
@@ -154,8 +154,8 @@ function menu_nodeapi(&$node, $op) {
         break;
 
       case 'delete':
-        menu_node_form_delete($node);
-        menu_rebuild();
+        drupal_delete('menu', 'query', "DELETE FROM {menu} WHERE path = '%s'", array('path' => 'node/'. $node->nid));
+        drupal_delete('menu', 'callback', array('menu_rebuild' => array()));
         break;
     }
   }
@@ -586,10 +586,10 @@ function menu_delete_item($item) {
   }
 
   if ($item['mid']) {
-    db_query('DELETE FROM {menu} WHERE mid = %d', $item['mid']);
+    drupal_delete('menu', 'query', 'DELETE FROM {menu} WHERE mid = %d', array('mid' => $item['mid']));
   }
   elseif ($item['path']) {
-    db_query("DELETE FROM {menu} WHERE path = '%s'", $item['path']);
+    drupal_delete('menu', 'query', "DELETE FROM {menu} WHERE path = '%s'", array('path' => $item['path']));
   }
 }
 

=== modified file 'modules/node/node.module'
--- modules/node/node.module	2007-03-17 20:38:56 +0000
+++ modules/node/node.module	2007-03-20 18:03:39 +0000
@@ -1575,28 +1575,64 @@ function theme_node_admin_nodes($form) {
 function node_multiple_delete_confirm() {
   $edit = $_POST;
 
-  $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
-  // array_filter returns only elements with TRUE values
-  foreach (array_filter($edit['nodes']) as $nid => $value) {
-    $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
-    $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#prefix' => '<li>', '#suffix' => check_plain($title) ."</li>\n");
+  $form['nodes']['#tree'] = TRUE;
+  $form['prefix'] = array('#value' => '<ul>');
+
+  // array_filter() returns only elements with TRUE values, array_keys() returns the nids.
+  $nids = array_keys(array_filter($edit['nodes']));
+  $nodes = db_query('SELECT nid, title, type FROM {node} WHERE nid IN(%s)', implode(', ', $nids));
+  while ($node = db_fetch_object($nodes)) {
+    $form['nodes'][$node->nid] = array(
+      '#type' => 'hidden',
+      '#value' => $node->nid
+    );
+    $form["node_$node->nid"] = array(
+      '#value' => check_plain($node->title),
+      '#prefix' => '<li>',
+      '#suffix' => "</li>\n",
+    );
+
+    // Start a new package for each node.
+    drupal_delete('node', 'new package');
+    drupal_delete('node', 'callback',
+      array(
+        'node_delete_post' => array($node->nid, $node->title, $node->type),
+      )
+    );
+    node_delete($node->nid);
   }
-  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+  $form['suffix'] = array('#value' => '</ul>');
 
-  return confirm_form($form,
-                      t('Are you sure you want to delete these items?'),
-                      'admin/content/node', t('This action cannot be undone.'),
-                      t('Delete all'), t('Cancel'));
+  $form['operation'] = array(
+    '#type' => 'hidden',
+    '#value' => 'delete',
+  );
+
+  // New package for the main admin callback.
+  drupal_delete('node', 'new package');
+
+  // Add the main callback for the mass deletion last.
+  drupal_delete('admin_node', 'callback',
+    array(
+      'node_multiple_delete_confirm_post' => array(),
+    )
+  );
+
+  return drupal_delete('admin_node', 'confirm',
+            array(
+              'form' => $form,
+              'question' => t('Are you sure you want to delete these items?'),
+              'path' => 'admin/content/node',
+              'yes' => t('Delete all'),
+              'destination' => 'admin/content/node',
+            )
+          );
 }
 
-function node_multiple_delete_confirm_submit($form_id, $form_values) {
-  if ($form_values['confirm']) {
-    foreach ($form_values['nodes'] as $nid => $value) {
-      node_delete($nid);
-    }
-    drupal_set_message(t('The items have been deleted.'));
-  }
-  return 'admin/content/node';
+function node_multiple_delete_confirm_post() {
+  // Clear the cache so an anonymous poster can see the nodes being deleted.
+  cache_clear_all();
+  drupal_set_message(t('The items have been deleted.'));
 }
 
 /**
@@ -1679,32 +1715,44 @@ function node_revision_revert($nid, $rev
  * revision is deleted.
  */
 function node_revision_delete($nid, $revision) {
-  if (user_access('administer nodes')) {
-    $node = node_load($nid);
-    if (node_access('delete', $node)) {
-      // Don't delete the current revision
-      if ($revision != $node->vid) {
-        $node = node_load($nid, $revision);
-
-        db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
-        node_invoke_nodeapi($node, 'delete revision');
-        drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision)));
-        watchdog('content', t('@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision)));
-      }
+  $node = node_load($nid);
 
-      else {
-        drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
-      }
-      if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) {
-        drupal_goto("node/$nid/revisions");
-      }
-      else {
-        drupal_goto("node/$nid");
-      }
+  if (user_access('administer nodes') && node_access('delete', $node)) {
+
+    // Don't delete the current revision
+    if ($revision != $node->vid) {
+      $node = node_load($nid, $revision);
+
+      drupal_delete('node_revisions', 'new package');
+      drupal_delete('node_revisions', 'callback', array('node_revision_delete_post' => array($node, $revision)));
+      drupal_delete('node_revisions', 'query', 'DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d', array('nid' => $nid, 'vid' => $revision));
+      node_invoke_nodeapi($node, 'delete revision');
+      drupal_delete('node_revisions', 'execute');
     }
+    else {
+      drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
+    }
+  }
+  else {
+    drupal_access_denied();
   }
+}
 
-  drupal_access_denied();
+/**
+ * Post revision deletion opertations.
+ */
+function node_revision_delete_post($node, $revision) {
+
+  drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision)));
+  watchdog('content', t('@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision)));
+
+
+  if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) > 1) {
+    drupal_goto("node/$node->nid/revisions");
+  }
+  else {
+    drupal_goto("node/$node->nid");
+  }
 }
 
 /**
@@ -2250,24 +2298,25 @@ function node_form_submit($form_id, $for
  * Menu callback -- ask for confirmation of node deletion
  */
 function node_delete_confirm($node) {
- $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
 
- return 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.'),
-   t('Delete'), t('Cancel'));
-}
+  drupal_delete('node', 'new package');
+  drupal_delete('node', 'callback',
+    array(
+      'node_delete_post' => array($node->nid, $node->title, $node->type),
+      // Clear the cache so an anonymous poster can see the node being deleted.
+      'cache_clear_all' => array(),
+    )
+  );
 
-/**
- * Execute node deletion
- */
-function node_delete_confirm_submit($form_id, $form_values) {
-  if ($form_values['confirm']) {
-    node_delete($form_values['nid']);
-  }
+  node_delete($node->nid, FALSE);
 
-  return '<front>';
+  return drupal_delete('node', 'confirm',
+    array(
+      'question' => t('Are you sure you want to delete %title?', array('%title' => $node->title)),
+      'path' => isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid,
+      'destination' => isset($_GET['destination']) ? $_GET['destination'] : '<front>',
+    )
+  );
 }
 
 /**
@@ -2278,23 +2327,24 @@ function node_delete($nid) {
   $node = node_load($nid);
 
   if (node_access('delete', $node)) {
-    db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
-    db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
+    $args = array('nid' => $node->nid);
+    drupal_delete('node', 'query', 'DELETE FROM {node} WHERE nid = %d', $args);
+    drupal_delete('node_revision', 'query', 'DELETE FROM {node_revisions} WHERE nid = %d', $args);
 
     // 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();
+function node_delete_post($nid, $title, $type) {
 
-    // Remove this node from the search index if needed.
-    if (function_exists('search_wipe')) {
-      search_wipe($node->nid, 'node');
-    }
-    drupal_set_message(t('%title has been deleted.', array('%title' => $node->title)));
-    watchdog('content', t('@type: deleted %title.', array('@type' => t($node->type), '%title' => $node->title)));
+  // Remove this node from the search index if needed.
+  if (function_exists('search_wipe')) {
+    search_wipe($nid, 'node');
   }
+  drupal_set_message(t('%title has been deleted.', array('%title' => $title)));
+  watchdog('content', t('@type: deleted %title.', array('@type' => t($type), '%title' => $title)));
 }
 
 /**

=== modified file 'modules/path/path.module'
--- modules/path/path.module	2007-03-17 20:38:56 +0000
+++ modules/path/path.module	2007-03-20 18:03:40 +0000
@@ -134,12 +134,12 @@ function path_admin_delete($pid = 0) {
  */
 function path_set_alias($path = NULL, $alias = NULL, $pid = NULL) {
   if ($path && !$alias) {
-    db_query("DELETE FROM {url_alias} WHERE src = '%s'", $path);
+    drupal_delete('url_alias', 'query', "DELETE FROM {url_alias} WHERE src = '%s'", array('src' => $path));
     db_query("UPDATE {menu} SET link_path = path WHERE path = '%s'", $path);
     drupal_clear_path_cache();
   }
   else if (!$path && $alias) {
-    db_query("DELETE FROM {url_alias} WHERE dst = '%s'", $alias);
+    drupal_delete('url_alias', 'query', "DELETE FROM {url_alias} WHERE dst = '%s'", array('dst' => $alias));
     db_query("UPDATE {menu} SET link_path = path WHERE link_path = '%s'", $alias);
     drupal_clear_path_cache();
   }

=== modified file 'modules/poll/poll.module'
--- modules/poll/poll.module	2007-02-12 22:26:41 +0000
+++ modules/poll/poll.module	2007-03-08 15:15:18 +0000
@@ -74,9 +74,9 @@ function poll_cron() {
  * 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);
+  drupal_delete('poll', 'query', 'DELETE FROM {poll} WHERE nid = %d', array('nid' => $node->nid));
+  drupal_delete('poll_choices', 'query', 'DELETE FROM {poll_choices} WHERE nid = %d', array('nid' => $node->nid));
+  drupal_delete('poll_votes', 'query', 'DELETE FROM {poll_votes} WHERE nid = %d', array('nid' => $node->nid));
 }
 
 /**

=== modified file 'modules/statistics/statistics.module'
--- modules/statistics/statistics.module	2007-02-28 07:01:52 +0000
+++ modules/statistics/statistics.module	2007-03-08 15:33:42 +0000
@@ -527,7 +527,7 @@ function statistics_nodeapi(&$node, $op,
   switch ($op) {
     case 'delete':
       // clean up statistics table when node is deleted
-      db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
+      drupal_delete('node_counter', 'query', 'DELETE FROM {node_counter} WHERE nid = %d', array('nid' => $node->nid));
   }
 }
 

=== modified file 'modules/system/system.module'
--- modules/system/system.module	2007-03-17 20:38:56 +0000
+++ modules/system/system.module	2007-03-20 18:09:04 +0000
@@ -2128,21 +2128,26 @@ function system_node_type($op, $info) {
  *   block <em>foo</em>?").
  * @param $path
  *   The page to go to if the user denies the action.
- *   Can be either a drupal path, or an array with the keys 'path', 'query', 'fragment'.
- * @param $description
- *   Additional text to display (defaults to "This action cannot be undone.").
- * @param $yes
- *   A caption for the button which confirms the action (e.g. "Delete",
- *   "Replace", ...).
- * @param $no
- *   A caption for the link which denies the action (e.g. "Cancel").
- * @param $name
- *   The internal name used to refer to the confirmation item.
+ *   Can be either a Drupal path, or an array with the keys 'path', 'query', 'fragment'.
+ * @param $options
+ *   An associative array of options, with the following key/value pairs:
+ *    'description' => Additional text to display.
+ *                     Default is "This action cannot be undone".
+ *    'yes'         => A caption for the button which confirms the action (e.g. "Confirm",
+ *                     "Replace", ...). Default is "Delete".
+ *    'no'          => A caption for the link which denies the action (e.g. "Cancel").
+ *                     Default is "Cancel".
+ *    'name'        => The internal name used to refer to the confirmation item.
+ *                     Default is "confirm".
+ *    'destination' => A destination page to go to after form submission -- the value can
+ *                     be of any form of the $goto argument accepted by drupal_redirect().
+ *
  * @return
  *   The form.
  */
-function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, $no = NULL, $name = 'confirm') {
-  $description = isset($description) ? $description : t('This action cannot be undone.');
+function confirm_form($form, $question, $path, $options = array()) {
+  $description = isset($options['description']) ? $options['description'] : t('This action cannot be undone.');
+  $name = isset($options['name']) ? $options['name'] : 'confirm';
 
   // Prepare cancel link
   $query = $fragment = NULL;
@@ -2151,7 +2156,7 @@ function confirm_form($form, $question, 
     $fragment = isset($path['fragment']) ? $path['fragment'] : NULL;
     $path = isset($path['path']) ? $path['path'] : NULL;
   }
-  $cancel = l($no ? $no : t('Cancel'), $path, array('query' => $query, 'fragment' => $fragment));
+  $cancel = l(isset($options['no']) ? $options['no'] : t('Cancel'), $path, array('query' => $query, 'fragment' => $fragment));
 
   drupal_set_title($question);
 
@@ -2161,17 +2166,37 @@ function confirm_form($form, $question, 
   $form['#attributes'] = array('class' => 'confirmation');
   $form['description'] = array('#value' => $description);
   $form[$name] = array('#type' => 'hidden', '#value' => 1);
+  if (isset($options['destination'])) {
+    $form['destination'] = array('#type' => 'value', '#value' => $options['destination']);
+  }
 
   $form['actions'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
-  $form['actions']['submit'] = array('#type' => 'submit', '#value' => $yes ? $yes : t('Confirm'));
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => isset($options['yes']) ? $options['yes'] : t('Delete'));
   $form['actions']['cancel'] = array('#value' => $cancel);
-  $form['#submit']['confirm_form_submit'] = array();
-  $form['#validate']['confirm_form_validate'] = array();
-  $form['#theme'] = 'confirm_form';
+
+  // Register default validate/submit/theme functions.
+  if ($name == 'confirm') {
+    $form['#validate']['confirm_form_validate'] = array();
+    $form['#submit']['confirm_form_submit'] = array();
+    $form['#theme'] = 'confirm_form';
+  }
+
   return $form;
 }
 
 /**
+ * Executes confirmation pages.
+ */
+function confirm_form_submit($form_id, $form_values) {
+  if ($form_values['confirm']) {
+    drupal_delete('', 'execute');
+  }
+  if (isset($form_values['destination'])) {
+    return $form_values['destination'];
+  }
+}
+
+/**
  * Determine if a user is in compact mode.
  */
 function system_admin_compact_mode() {

=== modified file 'modules/taxonomy/taxonomy.module'
--- modules/taxonomy/taxonomy.module	2007-02-28 07:01:56 +0000
+++ modules/taxonomy/taxonomy.module	2007-03-08 15:35:39 +0000
@@ -853,14 +853,14 @@ function taxonomy_node_save($node, $term
  * Remove associations of a node to its terms.
  */
 function taxonomy_node_delete($node) {
-  db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid);
+  drupal_delete('term_node', 'query', 'DELETE FROM {term_node} WHERE nid = %d', array('nid' => $node->nid));
 }
 
 /**
  * Remove associations of a node to its terms.
  */
 function taxonomy_node_delete_revision($node) {
-  db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid);
+  drupal_delete('term_node', 'query', 'DELETE FROM {term_node} WHERE vid = %d', array('vid' => $node->vid));
 }
 
 /**

=== modified file 'modules/upload/upload.module'
--- modules/upload/upload.module	2007-03-07 02:22:16 +0000
+++ modules/upload/upload.module	2007-03-08 15:56:34 +0000
@@ -734,30 +734,43 @@ function upload_delete($node) {
 
   foreach ($files as $fid => $file) {
     // Delete all file revision information associated with the node
-    db_query('DELETE FROM {file_revisions} WHERE fid = %d', $fid);
-    file_delete($file->filepath);
+    drupal_delete('file_revisions', 'query', 'DELETE FROM {file_revisions} WHERE fid = %d', array('fid' => $fid));
   }
 
   // Delete all files associated with the node
-  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
+  drupal_delete('files', 'query', 'DELETE FROM {files} WHERE nid = %d', array('nid' => $node->nid));
+
+  // Register a callback to delete the files.
+  drupal_delete('files', 'callback', array('upload_delete_post' => array($files)));
 }
 
 function upload_delete_revision($node) {
   if (is_array($node->files)) {
+    $files = array();
     foreach ($node->files as $file) {
       // Check if the file will be used after this revision is deleted
       $count = db_result(db_query('SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d', $file->fid));
 
       // if the file won't be used, delete it
       if ($count < 2) {
-        db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
-        file_delete($file->filepath);
+        drupal_delete('files', 'query', 'DELETE FROM {files} WHERE fid = %d', array('fid' => $file->fid));
+        $files[$file->fid] = $file;
       }
     }
   }
 
-  // delete the revision
-  db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
+  // Delete the revision.
+  drupal_delete('file_revisions', 'query', 'DELETE FROM {file_revisions} WHERE vid = %d', array('vid' => $node->vid));
+
+  // Register a callback to delete the files.
+  drupal_delete('files', 'callback', array('upload_delete_post' => array($files)));
+}
+
+function upload_delete_post($files) {
+  foreach ($files as $file) {
+    // Delete all files associated with the node or revision.
+    file_delete($file->filepath);
+  }
 }
 
 function _upload_form($node) {

=== modified file 'themes/garland/template.php'
--- themes/garland/template.php	2007-03-21 17:17:44 +0000
+++ themes/garland/template.php	2007-03-12 14:43:38 +0000
@@ -86,3 +86,11 @@ function phptemplate_menu_local_tasks() 
 
   return $output;
 }
+
+function garland_username($object) {
+  return _phptemplate_callback('username',
+   array(
+    'username' => check_plain($object->name),
+    'userpage' => l($object->name, 'user/'. $object->uid)));
+}
+

