=== modified file 'database/database.4.0.mysql'
--- database/database.4.0.mysql	
+++ database/database.4.0.mysql	
@@ -255,6 +255,43 @@ CREATE TABLE client_system (
 );
 
 --
+-- Table structure for table `deleted`
+--
+
+CREATE TABLE deleted (
+  did int(10) unsigned NOT NULL default '0',
+  uid int(10) unsigned NOT NULL default '0',
+  rid varchar(255) NOT NULL default '',
+  name varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  description varchar(255) NOT NULL default '',
+  title varchar(255) NOT NULL default '',
+  preview longtext NOT NULL,
+  data longtext NOT NULL,
+  timestamp int(10) unsigned NOT NULL default '0',
+  PRIMARY KEY  (did),
+  KEY uid (uid)
+);
+
+--
+-- Table structure for table `deleted_data`
+--
+
+CREATE TABLE deleted_data (
+  tid int(10) unsigned NOT NULL default '0',
+  did int(10) unsigned NOT NULL default '0',
+  dtable varchar(255) NOT NULL default '',
+  row_id int(10) unsigned NOT NULL default '0',
+  dcolumn varchar(255) NOT NULL default '',
+  data longtext NOT NULL,
+  PRIMARY KEY  (tid),
+  KEY did (did),
+  KEY dtable (dtable),
+  KEY row_id (row_id),
+  KEY dcolumn (dcolumn)
+);
+
+--
 -- Table structure for table 'files'
 --
 
=== modified file 'database/database.4.1.mysql'
--- database/database.4.1.mysql	
+++ database/database.4.1.mysql	
@@ -272,6 +272,45 @@ CREATE TABLE client_system (
 DEFAULT CHARACTER SET utf8;
 
 --
+-- Table structure for table `deleted`
+--
+
+CREATE TABLE deleted (
+  did int(10) unsigned NOT NULL default '0',
+  uid int(10) unsigned NOT NULL default '0',
+  rid varchar(255) NOT NULL default '',
+  name varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  description varchar(255) NOT NULL default '',
+  title varchar(255) NOT NULL default '',
+  preview longtext NOT NULL,
+  data longtext NOT NULL,
+  timestamp int(10) unsigned NOT NULL default '0',
+  PRIMARY KEY  (did),
+  KEY uid (uid)
+)
+DEFAULT CHARACTER SET utf8;
+
+--
+-- Table structure for table `deleted_data`
+--
+
+CREATE TABLE deleted_data (
+  tid int(10) unsigned NOT NULL default '0',
+  did int(10) unsigned NOT NULL default '0',
+  dtable varchar(255) NOT NULL default '',
+  row_id int(10) unsigned NOT NULL default '0',
+  dcolumn varchar(255) NOT NULL default '',
+  data longtext NOT NULL,
+  PRIMARY KEY  (tid),
+  KEY did (did),
+  KEY dtable (dtable),
+  KEY row_id (row_id),
+  KEY dcolumn (dcolumn)
+)
+DEFAULT CHARACTER SET utf8;
+
+--
 -- Table structure for table 'files'
 --
 
=== modified file 'database/database.pgsql'
--- database/database.pgsql	
+++ database/database.pgsql	
@@ -259,6 +259,43 @@ CREATE TABLE client_system (
 );
 
 --
+-- Table structure for table `deleted`
+--
+
+CREATE TABLE deleted (
+  did SERIAL,
+  uid integer NOT NULL default '0',
+  rid text NOT NULL default '',
+  name text NOT NULL default '',
+  url text NOT NULL default '',
+  description text NOT NULL default '',
+  title text NOT NULL default '',
+  preview text NOT NULL default '',
+  data text NOT NULL default '',
+  timestamp integer NOT NULL default '0',
+  PRIMARY KEY (did)
+);
+CREATE INDEX deleted_uid_idx ON deleted(uid);
+
+--
+-- Table structure for table `deleted_data`
+--
+
+CREATE TABLE deleted_data (
+  tid SERIAL,
+  did integer NOT NULL default '0',
+  dtable text NOT NULL default '',
+  row_id integer NOT NULL default '0',
+  dcolumn text NOT NULL default '',
+  data text NOT NULL default '',
+  PRIMARY KEY (tid)
+);
+CREATE INDEX deleted_data_did_idx ON deleted_data(did);
+CREATE INDEX deleted_data_dtable_idx ON deleted_data(dtable);
+CREATE INDEX deleted_data_row_id_idx ON deleted_data(row_id);
+CREATE INDEX deleted_data_dcolumn_idx ON deleted_data(dcolumn);
+
+--
 -- Table structure for table 'files'
 --
 
=== modified file 'database/updates.inc'
--- database/updates.inc	
+++ database/updates.inc	
@@ -2086,7 +2086,6 @@ function system_update_186() {
   return $ret;
 }
 
-
 function system_update_187() {
   // Increase the size of bid in boxes and aid in access
   $ret = array();
@@ -2102,3 +2101,74 @@ function system_update_187() {
   }
   return $ret;
 }
+
+function system_update_188() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysqli':
+    case 'mysql':
+      $ret[] = update_sql("CREATE TABLE {deleted} (
+                           did int(10) unsigned NOT NULL default '0',
+                           uid int(10) unsigned NOT NULL default '0',
+                           rid varchar(255) NOT NULL default '',
+                           name varchar(255) NOT NULL default '',
+                           url varchar(255) NOT NULL default '',
+                           description varchar(255) NOT NULL default '',
+                           title varchar(255) NOT NULL default '',
+                           preview longtext NOT NULL,
+                           data longtext NOT NULL,
+                           timestamp int(10) unsigned NOT NULL default '0',
+                           PRIMARY KEY  (did),
+                           KEY uid (uid)
+                         ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      $ret[] = update_sql("CREATE TABLE {deleted_data} (
+                           tid int(10) unsigned NOT NULL default '0',
+                           did int(10) unsigned NOT NULL default '0',
+                           dtable varchar(255) NOT NULL default '',
+                           row_id int(10) unsigned NOT NULL default '0',
+                           dcolumn varchar(255) NOT NULL default '',
+                           data longtext NOT NULL,
+                           PRIMARY KEY  (tid),
+                           KEY did (did),
+                           KEY dtable (dtable),
+                           KEY row_id (row_id),
+                           KEY dcolumn (dcolumn)
+                         ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */;");
+      break;
+    case 'pgsql':
+      $ret[] = update_sql("CREATE TABLE {deleted} (
+                           did SERIAL,
+                           uid integer NOT NULL default '0',
+                           rid text NOT NULL default '',
+                           name text NOT NULL default '',
+                           url text NOT NULL default '',
+                           description text NOT NULL default '',
+                           title text NOT NULL default '',
+                           preview text NOT NULL default '',
+                           data text NOT NULL default '',
+                           timestamp integer NOT NULL default '0',
+                           PRIMARY KEY (did)
+                          )");
+      $ret[] = update_sql("CREATE INDEX {deleted}_uid_idx ON deleted(uid)");
+      $ret[] = update_sql("CREATE TABLE {deleted_data} (
+                           tid SERIAL,
+                           did integer NOT NULL default '0',
+                           dtable text NOT NULL default '',
+                           row_id integer NOT NULL default '0',
+                           dcolumn text NOT NULL default '',
+                           data text NOT NULL default '',
+                           PRIMARY KEY (tid)
+                          )");
+      $ret[] = update_sql("CREATE INDEX {deleted_data}_did_idx ON deleted_data(did)");
+      $ret[] = update_sql("CREATE INDEX {deleted_data}_dtable_idx ON deleted_data(dtable)");
+      $ret[] = update_sql("CREATE INDEX {deleted_data}_row_id_idx ON deleted_data(row_id)");
+      $ret[] = update_sql("CREATE INDEX {deleted_data}_dcolumn_idx ON deleted_data(dcolumn)");
+      break;
+    default:
+      break;
+  }
+  return $ret;
+}
+
+
=== modified file 'includes/locale.inc'
--- includes/locale.inc	
+++ includes/locale.inc	
@@ -431,10 +431,12 @@ function _locale_string_edit_submit($for
  * Delete a language string.
  */
 function _locale_string_delete($lid) {
-  db_query('DELETE FROM {locales_source} WHERE lid = %d', $lid);
-  db_query('DELETE FROM {locales_target} WHERE lid = %d', $lid);
+  $did = next_delete_id();
+  $message = t('The string has been moved to the %trash.', array('%trash' => l(t('trash'), 'admin/trash')));
+  $trash_data = array('did' => $did, 'rid' => $lid, 'name' => 'locale_string', 'url' => 'admin/locale/string/search', 'description' => 'translation string', 'title' => 'translation string', 'message' => $message);
+  system_trash($trash_data, 'DELETE FROM {locales_source} WHERE lid = %d', $lid);
+  system_trash(array(), 'DELETE FROM {locales_target} WHERE lid = %d', $lid);
   locale_refresh_cache();
-  drupal_set_message(t('The string has been removed.'));
 
   drupal_goto('admin/locale/string/search');
 }
=== modified file 'misc/drupal.css'
--- misc/drupal.css	
+++ misc/drupal.css	
@@ -458,6 +458,16 @@ img.screenshot {
 #tracker table {
   width: 100%;
 }
+#trash-preview-info {
+  list-style: none;
+  padding: 0;
+}
+.trash-preview-title {
+  font-weight: bold;
+}
+.trash-preview-data {
+  display: inline;
+}
 .theme-settings-left {
   float: left;
   width: 49%;
=== modified file 'modules/block.module'
--- modules/block.module	
+++ modules/block.module	
@@ -511,25 +511,24 @@ function block_box_add_submit($form_id, 
 }
 
 /**
- * Menu callback; confirm deletion of custom blocks.
+ * Deletion of custom blocks.
  */
 function block_box_delete($bid = 0) {
+
   $box = block_box_get($bid);
-  $form['info'] = array('#type' => 'hidden', '#value' => $box['info'] ? $box['info'] : $box['title']);
-  $form['bid'] = array('#type' => 'hidden', '#value' => $bid);
+  $did = next_delete_id();
 
-  return confirm_form('block_box_delete_confirm', $form, t('Are you sure you want to delete the block %name?', array('%name' => theme('placeholder', $box['info']))), 'admin/block', '', t('Delete'), t('Cancel'));
-}
+  //build trashbin preview
+  $preview = array('info' => $box['info'], 'body' => $box['body']);
+
+  $message = t('The block %name has been moved to the %trash.', array('%name' => theme('placeholder', $box['info']), '%trash' => l(t('trash'), 'admin/trash')));
+
+  $trash_data = array('did' => $did, 'rid' => $bid, 'name' => 'block', 'url' => "admin/block/configure/block/$bid", 'title' => $box['title'], 'preview' => $preview, 'message' => $message);
+  system_trash($trash_data, 'DELETE FROM {boxes} WHERE bid = %d', $bid);
 
-/**
- * Deletion of custom blocks.
- */
-function block_box_delete_confirm_submit($form_id, $form_values) {
-  db_query('DELETE FROM {boxes} WHERE bid = %d', $form_values['bid']);
-  drupal_set_message(t('The block %name has been removed.', array('%name' => theme('placeholder', $form_values['info']))));
   cache_clear_all();
-  return 'admin/block';
-};
+  drupal_goto('admin/block');
+}
 
 function block_box_form($edit = array()) {
   $form['info'] = array(
=== modified file 'modules/book.module'
--- modules/book.module	
+++ modules/book.module	
@@ -204,7 +204,7 @@ function book_update($node) {
  * Implementation of hook_delete().
  */
 function book_delete(&$node) {
-  db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+  system_trash(array(), 'DELETE FROM {book} WHERE nid = %d', $node->nid);
 }
 
 /**
@@ -340,8 +340,10 @@ function book_outline_submit($form_id, $
       drupal_set_message(t('The book outline has been updated.'));
       break;
     case t('Remove from book outline'):
-      db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
-      drupal_set_message(t('The post has been removed from the book.'));
+      $did = next_delete_id();
+      $message = t('The post has been removed from the book outline. The association has been placed in the %trash.', array('%trash' => l(t('trash'), 'admin/trash')));
+      $trash_data = array('did' => $did, 'rid' => $node->nid, 'name' => 'node', 'url' => "node/$node->nid", 'description' => 'book association', 'title' => $node->title, 'message' => $message);
+      system_trash($trash_data, 'DELETE FROM {book} WHERE nid = %d', $node->nid);
       break;
   }
   return "node/$node->nid";
@@ -483,10 +485,10 @@ function book_nodeapi(&$node, $op, $teas
       }
       break;
     case 'delete revision':
-      db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
+      system_trash(array(), 'DELETE FROM {book} WHERE vid = %d', $node->vid);
       break;
     case 'delete':
-      db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+      system_trash(array(), 'DELETE FROM {book} WHERE nid = %d', $node->nid);
       break;
   }
 }
=== modified file 'modules/comment.module'
--- modules/comment.module	
+++ modules/comment.module	
@@ -310,8 +310,8 @@ 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);
+      system_trash(array(), 'DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
+      system_trash(array(), 'DELETE FROM {comments} WHERE nid = %d', $node->nid);
       break;
 
     case 'update index':
@@ -920,12 +920,9 @@ function comment_delete($cid) {
   $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) && ctype_digit($comment->cid) && $_POST['edit']['confirm']) {
-    drupal_set_message(t('The comment and all its replies have been deleted.'));
+  if (is_object($comment) && ctype_digit($comment->cid)) {
 
     // Delete comment and its replies.
     _comment_delete_thread($comment);
@@ -937,20 +934,10 @@ function comment_delete($cid) {
 
     drupal_goto("node/$comment->nid");
   }
-  else if (is_object($comment) && ctype_digit($comment->cid)) {
-    $output = confirm_form('comment_confirm_delete',
-                    array(),
-                    t('Are you sure you want to delete the comment %title?', array('%title' => theme('placeholder', $comment->subject))),
-                    'node/'. $comment->nid,
-                    t('Any replies to this comment will be lost. This action cannot be undone.'),
-                    t('Delete'),
-                    t('Cancel'));
-  }
   else {
     drupal_set_message(t('The comment no longer exists.'));
   }
 
-  return $output;
 }
 
 /**
@@ -987,7 +974,7 @@ function comment_admin_overview($type = 
   $edit = $_POST['edit'];
 
   if ($edit['operation'] == 'delete') {
-    return comment_multiple_delete_confirm();
+    return comment_multiple_delete($edit['comments']);
   }
 
   // build an 'Update options' form
@@ -1094,43 +1081,14 @@ function theme_comment_admin_overview($f
 }
 
 /**
- * List the selected comments and verify that the admin really wants to delete
- * them.
+ * Perform admin deletion of multiple comments.
  */
-function comment_multiple_delete_confirm() {
-  $edit = $_POST['edit'];
-
-  $form['comments'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE);
-  // array_filter() returns only elements with actual values
-  $comment_counter = 0;
-  foreach (array_filter($edit['comments']) as $cid => $value) {
-    $comment = _comment_load($cid);
-    if (is_object($comment) && ctype_digit($comment->cid)) {
-      $subject = db_result(db_query('SELECT subject FROM {comments} WHERE cid = %d', $cid));
-      $form['comments'][$cid] = array('#type' => 'hidden', '#value' => $cid, '#prefix' => '<li>', '#suffix' => check_plain($subject) .'</li>');
-      $comment_counter++;
-    }
-  }
-  $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
+function comment_multiple_delete($comments) {
 
-  if (!$comment_counter) {
-    drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.'));
-    drupal_goto('admin/comment');
-  }
-  else {
-    return confirm_form('comment_multiple_delete_confirm', $form,
-                        t('Are you sure you want to delete these comments and all their children?'),
-                        'admin/comment', t('This action cannot be undone.'),
-                        t('Delete comments'), t('Cancel'));
-  }
-}
+  $comments = array_filter($comments);
 
-/**
- * Perform the actual comment deletion.
- */
-function comment_multiple_delete_confirm_submit($form_id, $edit) {
-  if ($edit['confirm']) {
-    foreach ($edit['comments'] as $cid => $value) {
+  if (count($comments)) {
+    foreach ($comments as $cid => $value) {
       $comment = _comment_load($cid);
       _comment_delete_thread($comment);
       _comment_update_node_statistics($comment->nid);
@@ -1138,6 +1096,10 @@ function comment_multiple_delete_confirm
     }
     drupal_set_message(t('The comments have been deleted.'));
   }
+  else {
+    drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.'));
+  }
+
   drupal_goto('admin/comment');
 }
 
@@ -1657,14 +1619,30 @@ function theme_comment_post_forbidden($n
 }
 
 function _comment_delete_thread($comment) {
+
   if (!is_object($comment) || !ctype_digit($comment->cid)) {
     watchdog('content', t('Can not delete non-existent comment.'), WATCHDOG_WARNING);
     return;
   }
 
+  static $did;
+
+  // Build trash preview
+  $preview = array('comment' => $comment->comment);
+
+  $message = t('Comment: moved %subject and it\'s replies to the %trash.', array('%subject' => theme('placeholder', $comment->subject), '%trash' => l(t('trash'), 'admin/trash')));
+
+  // Continue in same deletion package if $did already exists, else start a new package
+  if ($did) {
+    $trash_data = array('preview' => $preview);
+  }
+  else {
+    $did = next_delete_id();
+    $trash_data = array('did' => $did, 'rid' => $comment->nid, 'name' => 'comment', 'url' => "node/$comment->nid#comment-$comment->cid", 'title' => $comment->subject, 'preview' => $preview, 'message' => $message);
+  }
   // Delete the comment:
-  db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
-  watchdog('content', t('Comment: deleted %subject.', array('%subject' => theme('placeholder', $comment->subject))));
+  system_trash($trash_data, 'DELETE FROM {comments} WHERE cid = %d', $comment->cid);
+  watchdog('content', $message);
 
   comment_invoke_comment($comment, 'delete');
 
=== modified file 'modules/contact.module'
--- modules/contact.module	
+++ modules/contact.module	
@@ -257,31 +257,20 @@ function contact_admin_edit_submit($form
 }
 
 /**
- * Category delete page.
+ * Category delete callback.
  */
 function contact_admin_delete($cid = NULL) {
   if ($info = db_fetch_object(db_query("SELECT category FROM {contact} WHERE cid = %d", $cid))) {
-    $form['category'] = array('#type' => 'value',
-      '#value' => $info->category,
-    );
-
-    return confirm_form('contact_admin_delete', $form, t('Are you sure you want to delete %category?', array('%category' => theme('placeholder', $info->category))), 'admin/contact', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
+    $did = next_delete_id();
+    $message = t('Contact category %category moved to the %trash.', array('%category' => theme('placeholder', $info->category), '%trash' => l(t('trash'), 'admin/trash')));
+    $trash_data = array('did' => $did, 'rid' => $cid, 'name' => 'contact', 'url' => "admin/contact/edit/$cid", 'description' => 'contact form', 'title' => $info->category, 'message' => $message);
+    system_trash($trash_data, "DELETE FROM {contact} WHERE cid = %d", $cid);
+    watchdog('mail', t('Contact form: category %category deleted.', array('%category' => theme('placeholder', $info->category))), WATCHDOG_NOTICE);
   }
   else {
     drupal_set_message(t('Category not found.'), 'error');
-    drupal_goto('admin/contact');
   }
-}
-
-/**
- * Process category delete form submission.
- */
-function contact_admin_delete_submit($form_id, $form_values) {
-  db_query("DELETE FROM {contact} WHERE cid = %d", arg(4));
-  drupal_set_message(t('Category %category has been deleted.', array('%category' => theme('placeholder', $form_values['category']))));
-  watchdog('mail', t('Contact form: category %category deleted.', array('%category' => theme('placeholder', $form_values['category']))), WATCHDOG_NOTICE);
-
-  return 'admin/contact';
+  drupal_goto('admin/contact');
 }
 
 /**
=== modified file 'modules/forum.module'
--- modules/forum.module	
+++ modules/forum.module	
@@ -140,7 +140,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);
+      system_trash(array(), 'DELETE FROM {forum} WHERE vid = %d', $node->vid);
       break;
   }
 }
@@ -149,30 +149,65 @@ function forum_nodeapi(&$node, $op, $tea
  * Implementation of hook_taxonomy().
  */
 function forum_taxonomy($op, $type, $term = NULL) {
+  static $repeat;
+
   if ($op == 'delete' && $term['vid'] == _forum_get_vid()) {
     switch ($type) {
       case 'term':
         $results = db_query('SELECT f.nid FROM {forum} f WHERE f.tid = %d', $term['tid']);
         while ($node = db_fetch_object($results)) {
           // node_delete will also remove any association with non-forum vocabularies.
-          node_delete($node->nid);
+          $node = node_load($node->nid);
+          $node->did = $term['did'];
+          node_delete($node);
         }
 
         // For containers, remove the tid from the forum_containers variable.
         $containers = variable_get('forum_containers', array());
         $key = array_search($term['tid'], $containers);
         if ($key !== FALSE) {
+          // Save the original term set for the trashbin metadata
+          if (!$repeat) {
+            $trash_data['forum_containers'] = $containers;
+            $repeat = TRUE;
+          }
           unset($containers[$key]);
         }
         variable_set('forum_containers', $containers);
         break;
       case 'vocabulary':
+        // Save the original vid for the trashbin metadata
+        $trash_data['forum_nav_vocabulary'] = variable_get('forum_nav_vocabulary', '');
         variable_del('forum_nav_vocabulary');
+        break;
+    }
+    // If there is metadata for the trashbin, pass it here via a dummy query,
+    if (isset($trash_data)) {
+      system_trash($trash_data, 'DELETE FROM {cache} WHERE 1 = 0');
     }
   }
 }
 
 /**
+ * Implementation of hook_system_trash_recover
+ *
+ * Processes forum metadata for the package being recovered.
+ *
+ * @param $trash_data
+ *   Metadata for the package being recovered.
+ */
+function forum_system_trash_recover($trash_data) {
+
+  // Restore original vocab and/or container settings if necessary
+  if (isset($trash_data['data']['forum_containers'])) {
+    	variable_set('forum_containers', $trash_data['data']['forum_containers']);
+  }
+  if (isset($trash_data['data']['forum_nav_vocabulary'])) {
+    	variable_set('forum_nav_vocabulary', $trash_data['data']['forum_nav_vocabulary']);
+  }
+}
+
+/**
  * Implementation of hook_settings
  */
 function forum_admin_configure() {
@@ -419,7 +454,7 @@ function forum_insert($node) {
  * Implementation of hook_delete().
  */
 function forum_delete(&$node) {
-  db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
+  system_trash(array(), 'DELETE FROM {forum} WHERE nid = %d', $node->nid);
 }
 
 /**
@@ -429,8 +464,8 @@ function forum_delete(&$node) {
  */
 function forum_form_container($edit = array()) {
   // Handle a delete operation.
-  if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
-    return _forum_confirm_delete($edit['tid']);
+  if ($_POST['op'] == t('Delete')) {
+    return _forum_delete_forum($edit['tid']);
   }
 
   $form['name'] = array(
@@ -477,8 +512,8 @@ function forum_form_container($edit = ar
  */
 function forum_form_forum($edit = array()) {
   // Handle a delete operation.
-  if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
-    return _forum_confirm_delete($edit['tid']);
+  if ($_POST['op'] == t('Delete')) {
+    return _forum_delete_forum($edit['tid']);
   }
 
   $form['name'] = array('#type' => 'textfield',
@@ -542,28 +577,14 @@ function forum_form_submit($form_id, $fo
 }
 
 /**
- * Returns a confirmation page for deleting a forum taxonomy term.
- *
- * @param $tid ID of the term to be deleted
+ * Deletes a forum.
  */
-function _forum_confirm_delete($tid) {
+function _forum_delete_forum($tid) {
   $term = taxonomy_get_term($tid);
+  taxonomy_del_term($tid);
+  watchdog('content', t('forum: deleted %term and all its sub-forums and associated posts.', array('%term' => theme('placeholder', $term->name))));
 
-  $form['tid'] = array('#type' => 'value', '#value' => $tid);
-  $form['name'] = array('#type' => 'value', '#value' => $term->name);
-
-  return confirm_form('forum_confirm_delete', $form, t('Are you sure you want to delete the forum %name?', array('%name' => theme('placeholder', $term->name))), 'admin/forums', t('Deleting a forum or container will delete all sub-forums and associated posts as well. This action cannot be undone.'), t('Delete'), t('Cancel'));
-}
-
-/**
- * Implementation of forms api _submit call. Deletes a forum after confirmation.
- */
-function forum_confirm_delete_submit($form_id, $form_values) {
-  taxonomy_del_term($form_values['tid']);
-  drupal_set_message(t('The forum %term and all sub-forums and associated posts have been deleted.', array('%term' => theme('placeholder', $form_values['name']))));
-  watchdog('content', t('forum: deleted %term and all its sub-forums and associated posts.', array('%term' => theme('placeholder', $form_values['name']))));
-
-  return 'admin/forum';
+  drupal_goto('admin/forum');
 }
 
 /**
=== modified file 'modules/locale.module'
--- modules/locale.module	
+++ modules/locale.module	
@@ -106,7 +106,7 @@ function locale_menu($may_cache) {
     // Language related callbacks
     $items[] = array('path' => 'admin/locale/language/delete',
       'title' => t('confirm'),
-      'callback' => 'locale_admin_manage_delete_form',
+      'callback' => 'locale_admin_manage_delete',
       'access' => $access,
       'type' => MENU_CALLBACK);
   }
@@ -314,9 +314,9 @@ function locale_admin_manage() {
 }
 
 /**
- * User interface for the language deletion confirmation screen.
+ * Menu callback for language deletion.
  */
-function locale_admin_manage_delete_form() {
+function locale_admin_manage_delete() {
   include_once './includes/locale.inc';
   $langcode = arg(4);
 
@@ -326,35 +326,26 @@ function locale_admin_manage_delete_form
     drupal_goto('admin/locale/language/overview');
   }
 
-  // For other locales, warn user that data loss is ahead.
   $languages = locale_supported_languages(FALSE, TRUE);
 
   if (!isset($languages['name'][$langcode])) {
     drupal_not_found();
   }
   else {
-    $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
-    return confirm_form('locale_admin_manage_delete_form', $form, t('Are you sure you want to delete the language %name?', array('%name' => theme('placeholder', t($languages['name'][$langcode])))), 'admin/locale/language/overview', t('Deleting a language will remove all data associated with it. This action cannot be undone.'), t('Delete'), t('Cancel'));
-  }
-}
+    $did = next_delete_id();
 
-/**
- * Process language deletion submissions.
- */
-function locale_admin_manage_delete_form_submit($form_id, $form_values) {
-  $languages = locale_supported_languages(FALSE, TRUE);
-  if (isset($languages['name'][$form_values['langcode']])) {
-    db_query("DELETE FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode']);
-    db_query("DELETE FROM {locales_target} WHERE locale = '%s'", $form_values['langcode']);
-    $message = t('The language %locale has been removed.', array('%locale' => theme('placeholder', t($languages['name'][$form_values['langcode']]))));
-    drupal_set_message($message);
+    $message = t('The language %locale has been moved to the %trash.', array('%locale' => theme('placeholder', t($languages['name'][$langcode])), '%trash' => l(t('trash'), 'admin/trash')));
     watchdog('locale', $message);
-  }
 
-  // Changing the locale settings impacts the interface:
-  cache_clear_all();
+    $trash_data = array('did' => $did, 'rid' => $langcode, 'name' => 'locale', 'url' => 'admin/locale', 'description' => 'translation', 'title' => $languages['name'][$langcode], 'message' => $message);
+    system_trash($trash_data, "DELETE FROM {locales_target} WHERE locale = '%s'", $langcode);
+    system_trash(array(), "DELETE FROM {locales_meta} WHERE locale = '%s'", $langcode);
 
-  return 'admin/locale/language/overview';
+    // ?Changing the locale settings impacts the interface.
+    cache_clear_all();
+
+    drupal_goto('admin/locale');
+  }
 }
 
 /**
=== modified file 'modules/node.module'
--- modules/node.module	
+++ modules/node.module	
@@ -836,6 +836,9 @@ function node_menu($may_cache) {
     $items[] = array('path' => 'admin/node', 'title' => t('content'),
       'callback' => 'node_admin_nodes',
       'access' => user_access('administer nodes'));
+    $items[] = array('path' => 'admin/node/delete', 'title' => t('content'),
+      'type' => MENU_CALLBACK, 'access' => user_access('administer nodes'),
+      'callback' => 'node_multiple_delete_confirm');
     $items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
       'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
 
@@ -1199,30 +1202,106 @@ function theme_node_admin_nodes($form) {
   return $output;
 }
 
+/**
+ * Deletes nodes from admin batch deletes, or presents a confirmation form.
+ *
+ * @return
+ *   A fully themed confimation form for all nodes which have extra data to display, or nothing if no nodes have extra
+ *   display data.
+ */
 function node_multiple_delete_confirm() {
+
   $edit = $_POST['edit'];
+  $options = array();
 
-  $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['operation'] = array('#type' => 'hidden', '#value' => 'delete');
-
-  return confirm_form('node_multiple_delete_confirm', $form,
-                      t('Are you sure you want to delete these items?'),
-                      'admin/node', t('This action cannot be undone.'),
-                      t('Delete all'), t('Cancel'));
-}
+  // Keep only checked node's nids
+  $nids = isset($edit['nodes']) ? array_keys($edit['nodes'], TRUE) : array();
+  foreach ($nids as $nid) {
 
-function node_multiple_delete_confirm_submit($form_id, $edit) {
-  if ($edit['confirm']) {
-    foreach ($edit['nodes'] as $nid => $value) {
-      node_delete($nid);
+    // Grab any extra display data for the node
+    $node = node_load($nid);
+    $extras = node_invoke_nodeapi($node, 'delete pre');
+
+    // No extras, just delete
+    if (!count($extras)) {
+      node_delete($node);
+    }
+    else {
+
+      // Add node and extras to the list only if no module has cancelled deletion for this node.
+      $title = check_plain($node->title);
+      if (in_array(FALSE, $extras)) {
+        drupal_set_message(t('Deletion of %title cancelled', array('%title' => $title)), 'error');
+      }
+      else {
+        $form['title'][$node->nid] = array('#type' => 'markup', '#value' => $title);
+        $form['extras'][$node->nid] = $extras;
+        $options[$node->nid] = '';
+      }
     }
-    drupal_set_message(t('The items have been deleted.'));
   }
+
+  // Back to the admin page if no nodes need confimation, else return a confirm form.
+  if (!count($options)) {
+    drupal_goto('admin/node');
+  }
+  else {
+    $form['title']['#tree'] = TRUE;
+    $form['extras']['#tree'] = TRUE;
+    $form['nodes'] = array('#type' => 'checkboxes', '#options' => $options);
+    $form['#action'] =  url('admin/node/delete');
+    return confirm_form('node_multiple_delete_confirm', $form,
+                      t('The following items need to be reviewed'),
+                      'admin/node', t('Selected items will be moved to the trash.'),
+                      t('Delete selected'), t('Cancel'));
+  }
+}
+
+/**
+ * Themes the confirmation form for admin batch deletes.
+ *
+ * @param $form
+ *   The from array to be themed
+ * @return
+ *   An HTML string representing the confirmation form
+ */
+function theme_node_multiple_delete_confirm($form) {
+  $rows = array();
+  $header = array('');
+  $output = '';
+
+  // Build one row for each node that needs confirmed
+  foreach(array_keys($form['nodes']['#options']) as $nid) {
+    $rows[] = array('<div class="container-inline">'. '<b>'. form_render($form['title'][$nid]) .'</b>'.  form_render($form['nodes'][$nid]) .'</div>'. form_render($form['extras'][$nid]));
+    $rows[] = array('');
+  }
+  $output .= theme('table', $header, $rows);
+  $output .= form_render($form);
+  return $output;
+}
+
+/**
+ * Submits the form data for deletion.
+ *
+ * @param $form_id
+ *   The unique form identifier.
+ * @param $form_values
+ *   The constructed form array.
+ */
+function node_multiple_delete_confirm_submit($form_id, $form_values) {
+
+  // Only checked values will be deleted
+  foreach ($form_values['nodes'] as $nid => $value) {
+
+    // Merge extra confirm data with node
+    $node = node_load($nid);
+    $node = (array) $node;
+    $node = array_merge($node, $form_values['extras'][$nid]);
+    $node = (object) $node;
+
+    node_delete($node);
+  }
+  drupal_set_message(t('The items have been deleted.'));
   return 'admin/node';
 }
 
@@ -1344,13 +1423,28 @@ function node_revision_delete($nid, $rev
       // Don't delete the current revision
       if ($revision != $node->vid) {
         $node = node_load($nid, $revision);
+        $did = next_delete_id();
 
-        db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
+        $message = t('Revision %title %revision moved to the %trash.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision), '%trash' => l(t('trash'), 'admin/trash')));
+
+        // Get node output (filtered and with module-specific fields).
+        if (node_hook($node, 'view')) {
+          node_invoke($node, 'view', false, false);
+        }
+        else {
+          $node = node_prepare($node, false);
+        }
+
+        // Assemble trashbin preview
+        $preview = array('body' => $node->body);
+
+        $trash_data = array('did' => $did, 'rid' => $nid, 'name' => 'node revision', 'url' => "node/$nid/revisions/$revision/view", 'description' => $node->type, 'title' => $node->title, 'preview' => $preview, 'message' => $message);
+        system_trash($trash_data, "DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
+
+        $node->did = $did;
         node_invoke_nodeapi($node, 'delete revision');
-        drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
         watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
       }
-
       else {
         drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
       }
@@ -1870,30 +1964,65 @@ function node_form_submit($form_id, $edi
 }
 
 /**
- * Menu callback -- ask for confirmation of node deletion
+ * Menu callback -- deletes the node if no extra data is present
+ *
+ * @return
+ *   If extra data is present, returns a confirmation form
  */
 function node_delete_confirm() {
+
   $edit = $_POST['edit'];
-  $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
-  $node = node_load($edit['nid']);
+  $nid = $edit['nid'] ? $edit['nid'] : arg(1);
+  $node = node_load($nid);
 
   if (node_access('delete', $node)) {
     $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
-    $output = confirm_form('node_delete_confirm', $form,
-                   t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
-                   $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action cannot be undone.'),
-                   t('Delete'), t('Cancel')  );
-  }
 
-  return $output;
+    // Check for extra info from modules
+    $extras = node_invoke_nodeapi($node, 'delete pre');
+    $form['extras'] = $extras;
+
+    // No extras, just delete the node
+    if (!count($extras)) {
+      node_delete($node);
+      drupal_goto('node');
+    }
+    else {
+
+      // Cancel deletion if necessary, otherwise display confirm form
+      if (in_array(FALSE, $extras)) {
+        drupal_set_message(t('Deletion cancelled'), 'error');
+        drupal_goto('node/'. $node->nid .'/edit');
+      }
+      else {
+        return confirm_form('node_delete_confirm', $form,
+               t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
+               $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action will move the
+                 item to the trash'), t('Delete'), t('Cancel'));
+      }
+    }
+  }
 }
 
 /**
- * Execute node deletion
+ * Submits the form data for deletion.
+ *
+ * @param $form_id
+ *   The unique form identifier.
+ * @param $form_values
+ *   The constructed form array.
  */
 function node_delete_confirm_submit($form_id, $form_values) {
+
   if ($form_values['confirm']) {
-    node_delete($form_values['nid']);
+
+    // Merge extra confirm data with node
+    $node = node_load($form_values['nid']);
+    $node = (array) $node;
+    $node = array_merge($node, $form_values);
+    $node = (object) $node;
+
+    node_delete($node);
   }
 
   return '';
@@ -1901,29 +2030,68 @@ function node_delete_confirm_submit($for
 
 /**
  * Delete a node.
+ *
+ * @param $node
+ *   The node to delete. Can pass a node object, node array, or nid.
  */
-function node_delete($nid) {
+function node_delete($node) {
 
-  $node = node_load($nid);
+  // Load the node if nid or array is passed
+  if (is_numeric($node)) {
+    $node = node_load($node);
+  }
+  elseif (is_array($node)) {
+    $node = node_load($node['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);
+  // If the deletion package is just starting, add appropriate data to $node and $trash_data array
+  if (!$node->did) {
+    $node->did = next_delete_id();
+    $trash_data['rid'] = $node->nid;
+    $trash_data['name'] = 'node';
+    $trash_data['url'] = "node/$node->nid";
+    $trash_data['message'] = t('%title moved to the %trash.', array('%title' => theme('placeholder', $node->title), '%trash' => l(t('trash'), 'admin/trash')));
+  }
 
-    // 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();
-
-    // 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' => theme('placeholder', $node->title))));
-    watchdog('content', t('%type: deleted %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))));
+  $trash_data['did'] = $node->did;
+  $trash_data['title'] = $node->title;
+  $trash_data['description'] = $node->type;
+
+  // Get node output (filtered and with module-specific fields).
+  if (node_hook($node, 'view')) {
+    node_invoke($node, 'view', false, false);
+  }
+  else {
+    $node = node_prepare($node, false);
+  }
+  // Allow modules to change $node->body.
+  node_invoke_nodeapi($node, 'view', false, false);
+
+  // Add comments if present
+  $comments = '';
+  if (function_exists('comment_render') && $node->comment) {
+    $comments = comment_render($node);
+  }
+
+  // Assemble trashbin preview
+  $preview['body'] = $node->body . $comments;
+  $trash_data['preview'] = $preview;
+
+  system_trash($trash_data, 'DELETE FROM {node} WHERE nid = %d', $node->nid);
+  system_trash(array(), '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();
+
+  // Remove this node from the search index if needed.
+  if (function_exists('search_wipe')) {
+    search_wipe($node->nid, 'node');
   }
+  watchdog('content', t('%type: deleted %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))));
 }
 
 /**
=== modified file 'modules/path.module'
--- modules/path.module	
+++ modules/path.module	
@@ -58,7 +58,7 @@ function path_menu($may_cache) {
       'access' => user_access('administer url aliases'),
       'type' => MENU_CALLBACK);
     $items[] = array('path' => 'admin/path/delete', 'title' => t('delete alias'),
-      'callback' => 'path_admin_delete_confirm',
+      'callback' => 'path_admin_delete',
       'access' => user_access('administer url aliases'),
       'type' => MENU_CALLBACK);
     $items[] = array('path' => 'admin/path/list', 'title' => t('list'),
@@ -96,47 +96,39 @@ function path_admin_edit($pid = 0) {
 }
 
 /**
- * Menu callback; confirms deleting an URL alias
+ * Menu callback; handles deletion of an URL alias.
  **/
-function path_admin_delete_confirm($pid) {
-  $path = path_load($pid);
+function path_admin_delete($pid = 0) {
   if (user_access('administer url aliases')) {
-    $form['pid'] = array('#type' => 'value', '#value' => $pid);
-    $output = confirm_form('path_admin_delete_confirm', $form,
-  t('Are you sure you want to delete path alias %title?', array('%title' => theme('placeholder', $path['dst']))),
-   $_GET['destination'] ? $_GET['destination'] : 'admin/path', t('This action cannot be undone.'),
-  t('Delete'), t('Cancel') );
-  }
-
-  return $output;
-}
+    $did = next_delete_id();
+    $result = db_fetch_object(db_query('SELECT src, dst FROM {url_alias} WHERE pid = %d', $pid));
 
-/**
- * Execute URL alias deletion
- **/
-function path_admin_delete_confirm_submit($form_id, $form_values) {
-  if ($form_values['confirm']) {
-    path_admin_delete($form_values['pid']);
-    return 'admin/path';
-  }
-}
+    // Build trash preview
+    $preview = array('destination' => $result->dst);
 
-/**
- * Post-confirmation; delete an URL alias.
- */
-function path_admin_delete($pid = 0) {
-  db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
-  drupal_set_message(t('The alias has been deleted.'));
-}
+    $message = t('The alias has been moved to the %trash.', array('%trash' => l(t('trash'), 'admin/trash')));
 
+    $trash_data = array('did' => $did, 'rid' => $pid, 'name' => 'url alias', 'url' => "admin/path/edit/$pid", 'title' => $result->src, 'preview' => $preview, 'message' => $message);
+    system_trash($trash_data, 'DELETE FROM {url_alias} WHERE pid = %d', $pid);
 
+    drupal_goto('admin/path');
+  }
+  else {
+    drupal_access_denied();
+  }
+}
 
 /**
  * Set an aliased path for a given Drupal path, preventing duplicates.
  */
-function path_set_alias($path = NULL, $alias = NULL, $pid = NULL) {
+function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $node = NULL) {
   if ($path && !$alias) {
-    db_query("DELETE FROM {url_alias} WHERE src = '%s'", $path);
+    if ($node->did) {
+      system_trash(array(), "DELETE FROM {url_alias} WHERE src = '%s'", $path);
+    }
+    else {
+      db_query("DELETE FROM {url_alias} WHERE src = '%s'", $path);
+    }
     drupal_clear_path_cache();
   }
   else if (!$path && $alias) {
@@ -239,7 +231,7 @@ function path_nodeapi(&$node, $op, $arg)
       case 'delete':
         $path = "node/$node->nid";
         if (drupal_get_path_alias($path) != $path) {
-          path_set_alias($path);
+          path_set_alias($path, NULL, NULL, $node);
         }
         break;
     }
=== modified file 'modules/poll.module'
--- modules/poll.module	
+++ modules/poll.module	
@@ -84,10 +84,10 @@ 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);
+function poll_delete(&$node) {
+  system_trash(array(), "DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
+  system_trash(array(), "DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
+  system_trash(array(), "DELETE FROM {poll} WHERE nid = %d", $node->nid);
 }
 
 /**
=== modified file 'modules/profile.module'
--- modules/profile.module	
+++ modules/profile.module	
@@ -175,7 +175,7 @@ function profile_user($type, &$edit, &$u
     case 'categories':
       return profile_categories();
     case 'delete':
-      db_query('DELETE FROM {profile_values} WHERE uid = %d', $user->uid);
+      system_trash(array(), 'DELETE FROM {profile_values} WHERE uid = %d', $user->uid);
   }
 }
 
@@ -353,30 +353,28 @@ function profile_field_form_submit($form
  * Menu callback; deletes a field from all user profiles.
  */
 function profile_field_delete($fid) {
-  $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid));
+  $did = next_delete_id();
+  $field = db_fetch_object(db_query("SELECT title, explanation, category FROM {profile_fields} WHERE fid = %d", $fid));
+
   if (!$field) {
     drupal_not_found();
     return;
   }
-  $form['fid'] = array('#type' => 'value', '#value' => $fid);
-  $form['title'] = array('#type' => 'value', '#value' => $field->title);
 
-  return confirm_form('profile_field_delete', $form, t('Are you sure you want to delete the field %field?', array('%field' => theme('placeholder', $field->title))), 'admin/settings/profile', t('This action cannot be undone. If users have entered values into this field in their profile, these entries will also be deleted. If you want to keep the user-entered data, instead of deleting the field you may wish to <a href="%edit-field">edit this field</a> and change it to a %hidden-field so that it may only be accessed by administrators.', array('%edit-field' => url('admin/settings/profile/edit/' . $fid), '%hidden-field' => theme('placeholder', t('hidden profile field')))), t('Delete'), t('Cancel'));
-}
+  // Build trash preview
+  $preview = array('explanation' => $field->explanation, 'category' => $field->category);
 
-/**
- * Process a field delete form submission.
- */
-function profile_field_delete_submit($form_id, $form_values) {
-  db_query('DELETE FROM {profile_fields} WHERE fid = %d', $form_values['fid']);
-  db_query('DELETE FROM {profile_values} WHERE fid = %d', $form_values['fid']);
+  $message = t('The field %field has been moved to the %trash. If users have entered values into this field in their profile, these entries have also been moved to the trash. If you want to keep the user-entered data, instead of deleting the field you may wish to %restore it and edit the field, changing it to a \'hidden profile field\' so that it may only be accessed by administrators.', array('%field' => theme('placeholder', $field->title), '%restore' => l(t('restore'), 'admin/trash'), '%trash' => l(t('trash'), 'admin/trash')));
 
-  cache_clear_all();
+  $trash_data = array('did' => $did, 'rid' => $fid, 'name' => 'profile', 'url' => "admin/settings/profile/edit/$fid", 'description' => 'profile field', 'title' => $field->title, 'preview' => $preview, 'message' => $message);
+  system_trash($trash_data, 'DELETE FROM {profile_values} WHERE fid = %d', $fid);
+  system_trash(array(), 'DELETE FROM {profile_fields} WHERE fid = %d', $fid);
 
-  drupal_set_message(t('The field %field has been deleted.', array('%field' => theme('placeholder', $form_values['title']))));
-  watchdog('profile', t('Profile field %field deleted.', array('%field' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/profile'));
+  watchdog('profile', $message, WATCHDOG_NOTICE, l(t('view'), 'admin/settings/profile'));
 
-  return 'admin/settings/profile';
+  cache_clear_all();
+
+  drupal_goto('admin/settings/profile');
 }
 
 /**
=== modified file 'modules/statistics.module'
--- modules/statistics.module	
+++ modules/statistics.module	
@@ -488,8 +488,6 @@ 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);
+      system_trash(array(), 'DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
   }
 }
-
-
=== modified file 'modules/system.module'
--- modules/system.module	
+++ modules/system.module	
@@ -52,7 +52,7 @@ function system_help($section) {
  * Implementation of hook_perm().
  */
 function system_perm() {
-  return array('administer site configuration', 'access administration pages', 'select different theme');
+  return array('administer site configuration', 'administer trash', 'access administration pages', 'select different theme');
 }
 
 /**
@@ -91,6 +91,7 @@ function system_elements() {
  * Implementation of hook_menu().
  */
 function system_menu($may_cache) {
+  global $user;
   $items = array();
 
   if ($may_cache) {
@@ -141,6 +142,14 @@ function system_menu($may_cache) {
     }
     $items[] = array('path' => 'admin/modules', 'title' => t('modules'),
       'callback' => 'system_modules', 'access' => $access);
+
+    // Trash
+    $items[] = array('path' => 'admin/trash', 'title' => t('trash'),
+      'callback' => 'system_admin_trash', 'access' => $user->uid != 0);
+    $items[] = array('path' => 'trash/preview', 'title' => t('preview'),
+      'callback' => 'system_trash_preview', 'access' => $user->uid != 0, 'type' => MENU_CALLBACK);
+    $items[] = array('path' => 'trash/recover', 'title' => t('recover'),
+      'callback' => 'system_trash_recover_single', 'access' => $user->uid != 0, 'type' => MENU_CALLBACK);
   }
 
   return $items;
@@ -476,6 +485,22 @@ function system_view_general() {
     '#description' => t('Message to show visitors when the site is in off-line mode.')
   );
 
+  $options = array(0 => t('Never'),
+    86400 => t('1 day'),
+    604800 => t('1 week'),
+    2592000 => t('1 month'),
+    7776000 => t('3 months'),
+    15552000 => t('6 months'),
+    31104000 => t('1 year'),
+  );
+  $form['site_status']['trash_auto_delete'] = array(
+    '#type' => 'select',
+    '#title' => t('Delete trash items older than'),
+    '#default_value' => variable_get('trash_auto_delete', 2592000),
+    '#options' => $options,
+    '#description' =>  t('Trashbin items older than the specified value will be permanently deleted.<br />NOTE: Cron must be running to use this feature.')
+  );
+
   // String handling: report status and errors.
   $form['strings'] = array('#type' => 'fieldset', '#title' => t('String handling'), '#collapsible' => TRUE, '#collapsed' => TRUE);
   $form['strings'] = array_merge($form['strings'], unicode_settings());
@@ -507,6 +532,32 @@ function system_check_directory($form_el
 }
 
 /**
+ * Implementation of hook_cron.
+ */
+function system_cron() {
+
+  // Trashbin auto-delete
+  if ($interval = variable_get('trash_auto_delete', 2592000)) {
+
+    // Grab all packages older than current time minus interval
+    $delete = time() - $interval;
+    $result = db_query('SELECT did FROM {deleted} WHERE timestamp < %d', $delete);
+
+    // Compose all packages to delete
+    if (db_num_rows($result)) {
+      while ($did = db_fetch_object($result)) {
+        $dids[] = $did->did;
+      }
+      $in = substr(str_repeat('%d, ', count($dids)), 0, -2);
+
+      // Remove from tables
+      db_query("DELETE FROM {deleted} WHERE did IN($in)", $dids);
+      db_query("DELETE FROM {deleted_data} WHERE did IN($in)", $dids);
+    }
+  }
+}
+
+/**
  * Return the cron status and errors for admin/settings.
  */
 function system_cron_settings() {
@@ -1229,6 +1280,803 @@ function system_theme_settings($key = ''
 }
 
 /**
+ * Overview of trashbin items.
+ */
+function system_admin_trash() {
+
+  global $user;
+  $op = $_POST['op'];
+  if ($op && ($op != t('Recover'))) {
+    return system_trash_delete_confirm();
+  }
+  $form = array();
+  $header = array(
+    array('data' => NULL),
+    array('data' => t('Title'), 'field' => 'title'),
+    array('data' => t('Type'), 'field' => 'description'),
+    array('data' => t('Trashed'), 'field' => 'timestamp', 'sort' => 'desc'),
+    array('data' => t('Trashed by'), 'field' => 'username'),
+    array('data' => t('Operations'))
+  );
+  $order_by = tablesort_sql($header);
+
+  // Determine if user can access only own trashbin items, or all trashbin items
+  $view_all = user_access('administer trash');
+  if ($view_all) {
+    $query = 'SELECT d.*, u.name AS username FROM {deleted} d LEFT JOIN {users} u ON d.uid = u.uid'. $order_by;
+    $result = pager_query($query, 25, 0);
+  }
+  else {
+    $query = 'SELECT d.*, u.name AS username FROM {deleted} d LEFT JOIN {users} u ON d.uid = u.uid WHERE u.uid = %d'. $order_by;
+    $result = pager_query($query, 25, 0, NULL, $user->uid);
+  }
+  $destination = drupal_get_destination();
+
+  // Build table item for each deleted package
+  while ($recover = db_fetch_object($result)) {
+    $package_user = user_load(array('uid' => $recover->uid));
+    $preview = $recover->preview ? unserialize($recover->preview) : array();
+    if (($recover->name == 'node') || ($recover->name == 'node revision')) {
+      $recover->description = node_get_name($recover->description);
+    }
+    $items[$recover->did] = '';
+    $form['datetime'][$recover->did] = array('#type' => 'markup', '#value' => format_date($recover->timestamp, 'small'));
+    $form['type'][$recover->did] =  array('#type' => 'markup', '#value' => t($recover->description));
+    $form['title'][$recover->did] = array('#type' => 'markup', '#value' => check_plain($recover->title));
+    $form['username'][$recover->did] = array('#type' => 'markup', '#value' => theme('username', $package_user));
+    $form['preview'][$recover->did] = array('#type' => 'markup', '#value' => l(t('preview'), "trash/preview/$recover->did", array(), $destination));
+  }
+
+  // Build form only if items exist
+  if (isset($form['datetime'])) {
+    $form['dids'] = array('#type' => 'checkboxes', '#options' => $items);
+    $form['pager'] = array('#value' => theme('pager', NULL, 25, 0));
+    $form['buttons'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
+    $form['buttons']['recover'] = array('#type' => 'submit', '#value' => t('Recover'));
+    if ($view_all) {
+      $form['buttons']['delete'] = array('#type' => 'submit', '#value' => t('Delete selected'));
+      $form['buttons']['delete_all'] = array('#type' => 'submit', '#value' => t('Delete all'));
+    }
+  }
+
+
+  return drupal_get_form('system_admin_trash', $form);
+}
+
+/**
+ * Theme the admin trash form.
+ *
+ * @param $form
+ *   Form array to be themed.
+ * @return
+ *   A themed HTML string representing the form.
+ */
+function theme_system_admin_trash($form) {
+
+  // Overview table:
+  if (is_array($form['datetime'])) {
+    $output = form_render($form['buttons']);
+    $header = array(
+      array('data' => NULL),
+      array('data' => t('Title'), 'field' => 'title'),
+      array('data' => t('Type'), 'field' => 'description'),
+      array('data' => t('Trashed'), 'field' => 'timestamp', 'sort' => 'desc'),
+      array('data' => t('Trashed by'), 'field' => 'username'),
+      array('data' => t('Operations'))
+    );
+    foreach (element_children($form['datetime']) as $key) {
+      $row = array();
+      $row[] = form_render($form['dids'][$key]);
+      $row[] = form_render($form['title'][$key]);
+      $row[] = form_render($form['type'][$key]);
+      $row[] = form_render($form['datetime'][$key]);
+      $row[] = form_render($form['username'][$key]);
+      $row[] = form_render($form['preview'][$key]);
+      $rows[] = $row;
+    }
+    $output .= theme('table', $header, $rows);
+
+    // Add pager if necessary
+    if ($form['pager']['#value']) {
+      $output .= form_render($form['pager']);
+    }
+  }
+  elseif (arg(1) != 'preview')  {
+    $output = t('There are no items in your trash');
+  }
+
+  $output .= form_render($form);
+
+  return $output;
+}
+
+/**
+ * Move deleted items to trashbin storage tables, and delete data from their original table.
+ *
+ * This function manages the deletion of most drupal table data, in a way which allows
+ * subsequent restoration of the data. Deletion happens in 'packages', so that all data which
+ * is removed by any API calls, etc.is stored in the package.  Multiple calls can be made to
+ * this function in a deletion cycle, and the relevant data/meta-data will be managed/summed
+ * for the package.
+ *
+ * @param $trash_data
+ *   This is an associative array of metadata pertaining to the current deletion package, and
+ *   supports the following keys:
+ *
+ *      'did' =>  The package ID.  This is a required key for the call to system_trash which
+ *                begins the deletion package (the first call to the function), subsequent calls
+ *                to system_trash in the same page load will inherit the last passed did, unless
+ *                a new did is passed.  This means that calls to system_trash from any core hooks
+ *                do not need to pass this value.  If the call to system_trash is starting a new
+ *                package, a new did may be obtained by calling next_delete_id().  Core API's also
+ *                make the did available in the hook, ex. $node->did.
+ *
+ *      'rid' =>  Optional. The root id of the package. Useful in situations where a
+ *                package is primarily associated with a single core element. For example, for
+ *                node deletions this would be the relevant nid, for user deletions the relevant
+ *                uid, etc.
+ *
+ *      'name' => The internal name of the deletion type, ex. 'node', 'user'.  This should generally
+ *                be passed in the call to system_trash which begins the package deletion, and is
+ *                required to be passed at least once in the deletion of the package.
+ *
+ *      'url' =>  Optional. The relative URL to return the user to after package recovery, ex. "node/$nid".
+ *
+ *      'description' =>  Optional. The human-readable type description of the package, ex. 'blog',
+ *                        'user account'. If not passed at any point in the package deletion, then
+ *                        the 'name' parameter will be used for display to the user.
+ *
+ *      'title' =>  Optional. The human-readable title of the package, ex. $node->title, $user->name.
+ *                  This is generally passed in the call to system_trash which begins package deletion.
+ *                  Not required, and will default to the first element of the 'preview' parameter if
+ *                  available, or [Untitled] if not.
+ *
+ *      'preview' =>  Optional. An associative array of items to display in the package preview, key = title,
+ *                    value = description.  ex. 'body' => $node->body.  Multiple previews can be passed in
+ *                    successive calls to system_trash for the same package, and they will be summed to form
+ *                    the final preview.
+ *
+ *      'message' =>  Optional. A custom message to prepend to the delete message.  The string should be
+ *                    translated, and must be passed in the first system_trash call in the package.
+ *
+ * @param $query
+ *   The SQL query which will delete the data.
+ * @param $args
+ *   Additional arguments to pass to SQL delete query.
+ */
+function system_trash($trash_data, $query) {
+  global $user;
+  static $same_did;
+  static $package_data;
+  static $summed_preview;
+  static $data;
+  static $timestamp;
+  static $set_message;
+
+  // Timestamp is the same for all packages in a trash cycle--helps to prevent dependency conflicts from cron-based permanent deletion
+  if (!isset($timestamp)) {
+    $timestamp = time();
+  }
+
+  // If the did is the same or not set, merge the new data in--otherwise reset the package data.
+  $package_data = ($trash_data['did'] == $same_did || !isset($trash_data['did'])) ? array_merge($package_data, $trash_data) : $trash_data;
+
+  // Grab the args for the query
+  $all_args = func_get_args();
+  if (count($all_args) > 2) {
+    $args = array_slice($all_args, 2);
+  }
+  else {
+    $args = array();
+  }
+
+  // Turn delete into select and get result
+  $delete = preg_replace('/DELETE.*?FROM/i', 'SELECT * FROM', $query);
+  $result = db_query($delete, $args);
+
+  // Grab table name and store
+  $table = preg_match('/^DELETE FROM \{([^}]+)/', $query, $m);
+  $table  = $m[1];
+
+  // Loop through result inserting rows into deleted_data table
+  while ($insert_data = db_fetch_array($result)) {
+    $row_id = db_next_id('{deleted_data}_row_id');
+    foreach ($insert_data as $column => $value) {
+    	$tid = db_next_id('{deleted_data}_tid');
+    	db_query("INSERT INTO {deleted_data} VALUES(%d, %d, '%s', %d, '%s', '%s')", $tid, $package_data['did'], $table, $row_id, $column, $value);
+    }
+  }
+
+  // A new package is being prepared.
+  if (($same_did != $package_data['did'])) {
+
+    // Allow modules to add extra metadata
+    $data = module_invoke_all('system_trash', $package_data);
+    $set_message = TRUE;
+    $same_did = $package_data['did'];
+    $summed_preview = array();
+  }
+  // Calculate new preview summary
+  if (isset($trash_data['preview'])) {
+    $summed_preview[] = $trash_data['preview'];
+  }
+  $insert_preview = $summed_preview ? serialize($summed_preview) : '';
+
+  // If no description or title exists, then insert default values
+  $title = $package_data['title'] ? $package_data['title'] : (is_array($summed_preview[0]) ? array_shift($summed_preview[0]) : t('[Untitled]'));
+  $description = $package_data['description'] ? $package_data['description'] : $package_data['name'];
+  $did = $package_data['did'];
+
+  // Only one message for each did
+  if ($set_message) {
+    $set_message = FALSE;
+    $message = $package_data['message'] ? $package_data['message'] : '';
+    drupal_set_message(t('%message You may %recover the item. ', array('%title' => theme('placeholder', $title), '%message' => $message, '%recover' => l(t('recover'), "trash/recover/$did"))));
+  }
+
+  // Extract any extra metadata that has been passed, and include it.
+  $default_fields = array('did', 'rid', 'name', 'url', 'description', 'title', 'preview', 'message');
+  foreach ($trash_data as $key => $value) {
+  	if (in_array($key, $default_fields)) {
+  	  unset($trash_data[$key]);
+  	}
+  }
+  $data = array_merge($data, $trash_data);
+
+  // Update metadata
+  if (db_num_rows(db_query('SELECT did FROM {deleted} WHERE did = %d', $did))) {
+    db_query("UPDATE {deleted} SET rid = '%s', name = '%s', url = '%s', description = '%s', title = '%s', preview = '%s', data = '%s' WHERE did = %d",
+      $package_data['rid'], $package_data['name'], $package_data['url'], $description, $title, $insert_preview, serialize($data), $did);
+  }
+  else {
+    db_query("INSERT INTO {deleted} VALUES(%d, %d, '%s', '%s', '%s', '%s', '%s','%s', '%s', %d)", $did, $user->uid, $package_data['rid'], $package_data['name'], $package_data['url'], $description, $title, $insert_preview, serialize($data), $timestamp);
+  }
+  // Delete from original table
+  db_query($query, $args);
+}
+
+/**
+ * Menu callback, displays preview metadata for a deleted package.
+ *
+ * @param $did
+ *   Package id for which to display the preview.
+ * @return
+ *   A themed HTML string representing the preview.
+ */
+function system_trash_preview($did) {
+
+  global $user;
+  $op = $_POST['op'];
+  $result = db_query('SELECT * FROM {deleted} WHERE did = %d', $did);
+  $insert = db_fetch_object($result);
+
+  // Only allow trashbin admin and user who deleted package to view
+  $view_all = user_access('administer trash');
+  if ($view_all || $insert->uid == $user->uid) {
+    if (db_num_rows($result) == 1) {
+
+      if ($op == t('Delete')) {
+        return system_trash_delete_confirm();
+      }
+
+      // Compose buttons and package id
+      $output = '<div id="trash-preview">';
+      $node_preview = '';
+      $form['buttons'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
+      $form['buttons']['recover'] = array('#type' => 'submit', '#value' => t('Recover'));
+      if ($view_all) {
+        $form['buttons']['delete'] = array('#type' => 'button', '#button_type' => 'submit', '#value' => t('Delete'));
+      }
+      $form['dids']['#tree'] = TRUE;
+      $form['dids'][$did] = array('#type' => 'hidden', '#value' => $did);
+
+      $output .= drupal_get_form('system_trash_preview', $form, 'system_admin_trash');
+
+      // Grab preview data
+      $values = array();
+      $data = $insert->preview ? unserialize($insert->preview) : array();
+
+      drupal_set_title(t('Trash preview: %title (%type)', array('%title' => check_plain($insert->title), '%type' => t($insert->description))));
+      $header = array(array('data' => t('Data'), 'colspan' => '2'));
+      $rows = array();
+
+      // Loop through preview array and construct table rows
+      foreach ($data as $preview_section) {
+        foreach ($preview_section as $key => $value) {
+          if ((($insert->name == 'node') || ($insert->name == 'node revision')) && ($key == 'body')) {
+            $node_preview = $value;
+          }
+          else {
+            $rows[] = array('<div class="trash-item-title">'. t($key) . ':</div> ', '<div class="trash-item-data">'. filter_xss($value) .'</div>');
+          }
+        }
+      }
+
+      $package_user = user_load(array('uid' => $insert->uid));
+
+      $output .= '<ul id="trash-preview-info">';
+      $output .= '<li><span class="trash-preview-title">'. t('Type:') .'</span> <span class="trash-preview-data">'. t($insert->name) .'</span></li>';
+      $output .= '<li><span class="trash-preview-title">'. t('Trashed:') .'</span> <span class="trash-preview-data">'. format_date($insert->timestamp, 'small') .'</span></li>';
+      $output .= '<li><span class="trash-preview-title">'. t('Trashed by:') .'</span> <span class="trash-preview-data">'. theme('username', $package_user) .'</span></li>';
+      $output .= '</ul>';
+      $output .= $node_preview;
+      if (count($rows)) {
+        $output .= theme('table', $header, $rows);
+      }
+      $output .= '</div>';
+      return $output;
+    }
+    else {
+      drupal_not_found();
+    }
+  }
+  else {
+    drupal_access_denied();
+  }
+}
+
+/**
+ * Internal function -- restores the table data for the specified deleted packages.
+ *
+ * @param $dids
+ *   An array of package ids to restore.
+ * @return
+ *   An associative array of package IDs, each of which is an associative array with
+ *   the following keys:
+ *
+ *      'title' => The title of the package being recovered.
+ *      'url' => The relative URL of the recovered package.
+ */
+function system_trash_recover($dids) {
+
+  // Grab all data for all the specified packages, compose into a master array
+  $in = substr(str_repeat('%d, ', count($dids)), 0, -2);
+  $result = db_query("SELECT * FROM {deleted_data} WHERE did IN($in)", $dids);
+  $all_data = array();
+  while ($row = db_fetch_array($result)) {
+    $all_data[$row['did']][$row['row_id']][] = $row;
+  }
+
+  // Process data to elimate packages which fail dependency checks
+  if (isset($all_data)) {
+    $recover_data = system_trash_dependency_check($all_data);
+
+    // Recompose data array into table insert data, per row, and reinsert to orginal table
+    if ($recover_data) {
+      foreach ($recover_data as $did => $rows) {
+        foreach ($rows as $data) {
+          $table = $data[0]['dtable'];
+          $fields = array();
+          $values = array();
+          $data_values = array();
+          foreach ($data as $value) {
+            $fields[] = $value['dcolumn'];
+            $values[] = is_numeric($value['data']) ? '%d' : "'%s'";
+            $data_values[] = $value['data'];
+          }
+          $query_fields = implode(', ', $fields);
+          $query_values = implode(', ', $values);
+          db_query("INSERT INTO {$table} ($query_fields) VALUES($query_values)", $data_values);
+        }
+        $delete_dids[] = $did;
+        $trash_data = db_fetch_array(db_query('SELECT * FROM {deleted} WHERE did = %d', $did));
+
+        // Allow other modules to process recovery data for this package
+        $trash_data['data'] = $trash_data['data'] ? unserialize($trash_data['data']) : array();
+        module_invoke_all('system_trash_recover', $trash_data);
+
+        // Prepare dispaly data for messages
+        $recovery[$did]['title'] = $trash_data['title'];
+        $recovery[$did]['url'] = $trash_data['url'];
+      }
+
+      // Remove restored packages from deleted tables
+      $in = substr(str_repeat('%d, ', count($delete_dids)), 0, -2);
+      db_query("DELETE FROM {deleted} WHERE did IN($in)", $delete_dids);
+      db_query("DELETE FROM {deleted_data} WHERE did IN($in)", $delete_dids);
+    }
+  }
+  return $recovery;
+}
+
+/**
+ * Checks dependencies for all packages being recovered, and eliminates those which fail.
+ *
+ * Function is recursive, eliminating packages which fail dependency checks and repeatedly
+ * calling itself until only packages which pass checks remain.
+ *
+ * @param $recover_data
+ *   An array of package data for potential recovery.
+ * @return
+ *   The $recover_data array with all failed packages pruned.
+ */
+function system_trash_dependency_check($recover_data) {
+
+  // Run dependency checks per package, then per table row
+  $dids = array_keys($recover_data);
+  foreach ($recover_data as $did => $rows) {
+    foreach ($rows as $row => $data) {
+      $table = $data[0]['dtable'];
+
+      // If package fails checks, remove from the $recover_data array and call the function again
+      if (!system_trash_check_row_dependency($table, $data, $dids)) {
+        $title = db_result(db_query('SELECT description FROM {deleted} WHERE did = %d', $did));
+        drupal_set_message(t('%title cannot be recovered due to dependency errors. Check the errors and recover any data this
+          package depends on before attempting recovery.', array('%title' => theme('placeholder', $title))), 'error');
+
+        // Remove this package from recovery array and recurse through function
+        unset($recover_data[$data[0]['did']]);
+        return system_trash_dependency_check($recover_data);
+      }
+    }
+  }
+  return $recover_data;
+}
+
+/**
+ * Check dependencies for a single row of table data.
+ *
+ * All relevant dependencies are checked for the table in question.
+ *
+ * @param $table
+ *   The table for which depedencies will be checked.
+ * @param $data
+ *   An associative array containing the row data to be tested, key = column name, value = column data.
+ * @param $dids
+ *   An array containing all package id's being recovered.
+ * @return
+ *   TRUE if the row passes dependency checks, FALSE otherwise.
+ */
+function system_trash_check_row_dependency($table, $data, $dids) {
+
+  $return = TRUE;
+  $dependencies = system_get_dependencies($table);
+  foreach ($dependencies as $dependency) {
+    $value = NULL;
+
+    // Check for optional dependency bypass conditions
+    $dependency[3] = isset($dependency[3]) ? $dependency[3] : array();
+
+    // Find the relevant dependent column, and pull the value
+    foreach ($data as $array) {
+      if ($array['dcolumn'] == $dependency[0]) {
+        $value = $array['data'];
+      }
+    }
+
+    // Error if no value is found, as it's required to check the dependency
+    if (!isset($value)) {
+      $return = FALSE;
+      drupal_set_message(t('Dependency error: no matching key %key in table %table.  Dependency of table %dependency', array('%key' => $dependency[0],
+        '%table' => $table, '%dependency' => $dependency[2])), 'error');
+    }
+
+    // Check existing table data for the dependent data, then check all packages being restored, then check for bypass exceptions
+    else {
+      if (!system_check_dependency($dependency, $value) && !system_check_package_dependencies($dependency, $value, $dids) && !in_array($value, $dependency[3])) {
+        $return = FALSE;
+        drupal_set_message(t("Dependency error: value '%value' for key '%key' missing in table %dependency", array('%value' => $value,
+          '%key' => $dependency[1], '%dependency' => $dependency[2])), 'error');
+      }
+    }
+  }
+  return $return;
+}
+
+/**
+ * Checks a row dependency within the package group being recovered.
+ *
+ * @param $dependency
+ *   An array of column dependency data for the row being checked.
+ * @param $value
+ *   The row value of the dependent column being checked.
+ * @param $dids
+ *   An array of package id's being recovered.
+ * @return
+ *   TRUE value if the dependency passes, FALSE otherwise.
+ */
+function system_check_package_dependencies($dependency, $value, $dids) {
+
+  $field = $dependency[1];
+  $table = $dependency[2];
+  $in = substr(str_repeat('%d, ', count($dids)), 0, -2);
+  $args = array_merge(array($table), array($field), array($value), $dids);
+  return db_num_rows(db_query("SELECT tid FROM {deleted_data} WHERE dtable = '%s' AND dcolumn = '%s' AND data = '%s' AND did IN($in)",
+    $args));
+
+}
+
+/**
+ * Menu callback.  Recovers the specified single package.
+ *
+ * @param $did
+ *   The package id to be recovered.
+ */
+function system_trash_recover_single($did) {
+
+  $result = db_query('SELECT uid FROM {deleted} WHERE did = %d', $did);
+
+  // Only allow trash admins and the user who deleted the node to recover
+  if (user_access('administer trash') || $user->uid == db_result($result)) {
+    if (db_num_rows($result) == 1) {
+      $recovery = system_trash_recover(array($did));
+      if ($recovery[$did]) {
+        drupal_set_message(t('The item has been recovered'));
+      }
+      else {
+        drupal_set_message(t('The item was not recovered'), 'error');
+      }
+
+      if ($url = $recovery[$did]['url']) {
+        drupal_goto($url);
+      }
+      else {
+        drupal_goto();
+      }
+    }
+    else {
+      drupal_not_found();
+    }
+  }
+  else {
+    drupal_access_denied();
+  }
+}
+
+/**
+ * Validates the form data for trashbin recovery.
+ *
+ * @param $form_id
+ *   The unique form identifier.
+ * @param $form_values
+ *   The constructed form array.
+ */
+function system_admin_trash_validate($form_id, $form_values) {
+
+  $dids = $form_values['dids'] ? array_keys($form_values['dids'], TRUE) : array();
+  if ((count($dids) == 0)) {
+    drupal_set_message(t('No items were selected. Select items in order to perform the operation'), 'error');
+    drupal_goto('admin/trash');
+  }
+}
+
+/**
+ * Submits the form data for trashbin recovery.
+ *
+ * @param $form_id
+ *   The unique form identifier.
+ * @param $form_values
+ *   The constructed form array.
+ */
+function system_admin_trash_submit($form_id, $form_values) {
+
+  $dids = $form_values['dids'] ? array_keys($form_values['dids'], TRUE) : array();
+  $recovery = system_trash_recover($dids);
+
+  // Check for number of recovered items and provide appropriate message.
+  foreach ($dids as $did) {
+    if (isset($recovery[$did])) {
+      $title = check_plain($recovery[$did]['title']);
+      $url = $recovery[$did]['url'];
+      $array = $url ? array('%title' => l($title, $url)) : array('%title' => $title);
+      drupal_set_message(t('%title has been recovered', $array));
+    }
+    else {
+      $title = db_result(db_query('SELECT title FROM {deleted} WHERE did = %d', $did));
+      drupal_set_message(t('Recovery of %title unsuccessful', array('%title' => check_plain($title))), 'error');
+    }
+  }
+
+
+  return 'admin/trash';
+}
+
+/**
+ * Internal function.  Permanently delete the specified package.
+ *
+ * @param $did
+ *   Package id to be deleted.
+ */
+function system_trash_delete($did) {
+
+  $trash_data = db_fetch_array(db_query('SELECT * FROM {deleted} WHERE did = %d', $did));
+
+  // Allow other modules to process custom data prior to the deletion.
+  $trash_data['data'] = $trash_data['data'] ? unserialize($trash_data['data']) : array();
+  module_invoke_all('system_trash_delete', $trash_data);
+
+  db_query('DELETE FROM {deleted} WHERE did = %d', $did);
+  db_query('DELETE FROM {deleted_data} WHERE did = %d', $did);
+}
+
+/**
+ * Submits the form data for trashbin permanent deletion.
+ *
+ * @param $form_id
+ *   The unique form identifier.
+ * @param $form_values
+ *   The constructed form array.
+ */
+function system_trash_delete_confirm_submit($form_id, $form_values) {
+
+  // Specific packages were selected, so delete those
+  if (isset($form_values['dids'])) {
+    foreach ($form_values['dids'] as $did) {
+      system_trash_delete($did);
+    }
+  }
+
+  // 'Delete all' was selected
+  else {
+    $dids = db_query('SELECT did FROM {deleted}');
+    while ($did = db_fetch_object($dids)) {
+      $did = $did->did;
+      system_trash_delete($did);
+    }
+  }
+
+  drupal_set_message(t('Permanent delete successful'));
+  return 'admin/trash';
+}
+
+/**
+ * Internal function.  Prints confirmation form for permanent deletion.
+ *
+ * @return
+ *   An HTML string representing the confirmation form.
+ */
+function system_trash_delete_confirm() {
+
+  $dids = $_POST['edit']['dids'] ? array_keys($_POST['edit']['dids'], TRUE) : array();
+  $dids = ($_POST['op'] == t('Delete all')) ? array() : $dids;
+
+  if ((count($dids) == 0) && $_POST['op'] == t('Delete selected')) {
+    drupal_set_message(t('No items were selected. Select items in order to perform the operation'), 'error');
+    drupal_goto('admin/trash');
+  }
+
+  $form['dids']['#tree'] = TRUE;
+  foreach ($dids as $did) {
+  	$form['dids'][$did] = array('#type' => 'hidden', '#value' => $did);
+  }
+
+  if (count($dids) > 0) {
+
+    // List of titles to delete
+    $titles = array();
+    $in = substr(str_repeat('%d, ', count($dids)), 0, -2);
+    $result = db_query("SELECT title FROM {deleted} WHERE did IN($in)", $dids);
+    while ($package = db_fetch_object($result)) {
+      $titles[] = check_plain($package->title);
+    }
+    $form['titles'] = array('#value' => theme('item_list', $titles));
+
+    $message = t('Are you sure you want to permanently delete these items?');
+  }
+  else {
+    $message = t('Are you sure you want to delete all items in the trashbin?');
+  }
+
+  $output = confirm_form('system_trash_delete_confirm', $form, $message,
+                  'admin/trash', t('This operation cannot be undone.'),
+                  t('Delete'), t('Cancel'));
+  return $output;
+}
+
+/**
+ *  Pulls the next did from the sequences table.
+ */
+function next_delete_id() {
+  return db_next_id('{deleted}_did');
+}
+
+/**
+ * Implementation of hook_table_dependencies.
+ *
+ * @return
+ *   An array of table dependencies for drupal core.
+ */
+function system_table_dependencies() {
+  $dependencies = array();
+  $dependencies['accesslog'] = array(array('sid', 'sid', 'sessions'), array('uid', 'uid', 'users'));
+  $dependencies['aggregator_category_feed'] = array(array('cid', 'cid', 'aggregator_category'));
+  // NEED TO FIGURE OUT THE OTHER AGGREGATOR TABLE RELATIONSHIPS
+  $dependencies['authmap'] = array(array('uid', 'uid', 'users'));  //AID RELATED TO ANYTHING?
+  $dependencies['book'] = array(array('vid', 'vid', 'node_revisions'), array('nid', 'nid', 'node'));
+  $dependencies['comments'] = array(array('uid', 'uid', 'users'), array('nid', 'nid', 'node'), array('pid', 'cid', 'comments', array(0)));
+  $dependencies['files'] = array(array('nid', 'nid', 'node'));
+  $dependencies['file_revisions'] = array(array('vid', 'vid', 'node_revisions'));
+  $dependencies['filters'] = array(array('format', 'format', 'filter_formats'));
+  $dependencies['forum'] = array(array('nid', 'nid', 'node'), array('vid', 'vid', 'node_revisions'), array('tid', 'tid', 'term_data', array(0)));
+  $dependencies['history'] = array(array('uid', 'uid', 'users'), array('nid', 'nid', 'node_revisions'));
+  // LOCALES TABLES NEED ENTERED HERE
+  $dependencies['menu'] = array(array('pid', 'mid', 'menu'));
+  $dependencies['moderation_roles'] = array(array('rid', 'rid', 'role'), array('mid', 'mid', 'moderation_votes'));
+  $dependencies['node'] = array(array('vid', 'vid', 'node_revisions'), array('uid', 'uid', 'users'));
+  $dependencies['node_access'] = array(array('nid', 'nid', 'node'));  // GID RELATED TO ANYTHING?
+  $dependencies['node_comment_statistics'] = array(array('nid', 'nid', 'node'), array('last_comment_uid', 'uid', 'users'));
+  $dependencies['node_counter'] = array(array('nid', 'nid', 'node'));
+  $dependencies['node_revisions'] = array(array('nid', 'nid', 'node'), array('uid', 'uid', 'users'));
+  $dependencies['permission'] = array(array('rid', 'rid', 'role'));
+  $dependencies['poll'] = array(array('nid', 'nid', 'node'));
+  $dependencies['poll_choices'] = array(array('nid', 'nid', 'node'));
+  $dependencies['profile_values'] = array(array('fid', 'fid', 'profile_fields'), array('uid', 'uid', 'users'));
+  // SEARCH TABLES HERE
+  $dependencies['sessions'] = array(array('uid', 'uid', 'users'));  // SID RELATED TO ANYTHING?
+  $dependencies['term_data'] = array(array('vid', 'vid', 'vocabulary'));
+  $dependencies['term_hierarchy'] = array(array('tid', 'tid', 'term_data'), array('parent', 'tid', 'term_hierarchy', array(0)));
+  $dependencies['term_node'] = array(array('nid', 'nid', 'node'), array('tid', 'tid', 'term_data'));
+  $dependencies['term_relation'] = array(array('tid1', 'tid', 'term_data'), array('tid2', 'tid', 'term_data'));
+  $dependencies['term_synonym'] = array(array('tid', 'tid', 'term_data'));
+  $dependencies['users_roles'] = array(array('uid', 'uid', 'users'), array('rid', 'rid', 'role'));
+  $dependencies['vocabulary_node_types'] = array(array('vid', 'vid', 'vocabulary'));  //TYPE RELATED TO ANYTHING?
+  $dependencies['watchdog'] = array(array('uid', 'uid', 'users'));
+
+  return $dependencies;
+}
+
+/**
+ *  Builds an array of table dependencies for all enabled modules.
+ *
+ * @return
+ *   An array of table dependencies.
+ */
+function system_build_dependencies() {
+  static $dependencies;
+
+  if (!isset($dependencies)) {
+    // Build dependencies array
+    $dependencies = module_invoke_all('table_dependencies');
+  }
+  return $dependencies;
+}
+
+/**
+ * Provides dependency data for a specified table.
+ *
+ * @param $table
+ *   The table for which to pull dependencies.
+ * @return
+ *   A dependency array for the specified table.
+ */
+function system_get_dependencies($table) {
+
+  $dependencies = system_build_dependencies();
+  if (array_key_exists($table, $dependencies)) {
+    return $dependencies[$table];
+  }
+  else {
+    return array();
+  }
+}
+
+/**
+ *  Checks a single table dependency.
+ *
+ * @param $dependency
+ *   A single array element to check -- pulled from a table's dependency array generated by system_get_dependencies.
+ * @param $value
+ *   The value to check for in the dependency.  This is the value that must be present in the table being
+ *   checked in order for the row to pass this check.
+ * @return
+ *   TRUE if the dependent data is found, FALSE otherwise.
+ */
+function system_check_dependency($dependency, $value) {
+
+  $field = $dependency[1];
+  $table = $dependency[2];
+
+  if (is_numeric($value)) {
+    $result = db_num_rows(db_query('SELECT * FROM {%s} WHERE %s = %d', $table, $field, $value));
+  }
+  else {
+    $result = db_num_rows(db_query("SELECT * FROM {%s} WHERE %s = '%s'", $table, $field, $value));
+  }
+
+  return $result;
+}
+
+/**
  * Output a confirmation form
  *
  * This function outputs a complete form for confirming an action. A link is
=== modified file 'modules/taxonomy.module'
--- modules/taxonomy.module	
+++ modules/taxonomy.module	
@@ -322,13 +322,23 @@ function taxonomy_save_vocabulary(&$edit
 function taxonomy_del_vocabulary($vid) {
   $vocabulary = (array) taxonomy_get_vocabulary($vid);
 
-  db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
-  db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
+  $did = next_delete_id();
+
+  // Build trash preview
+  $preview = array('description' => $vocabulary['description']);
+
+  $message = t('Vocabulary %category and it\'s children moved to the %trash.', array('%category' => theme('placeholder', $vocabulary['name']), '%trash' => l(t('trash'), 'admin/trash')));
+
+  $trash_data = array('did' => $did, 'rid' => $vid, 'name' => 'vocabulary', 'url' => "admin/taxonomy/edit/vocabulary/$vid", 'description' => 'vocabulary term', 'title' => $vocabulary['name'], 'preview' => $preview, 'message' => $message);
+  system_trash($trash_data, 'DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
+  system_trash(array(), 'DELETE FROM {vocabulary} WHERE vid = %d', $vid);
+
   $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
   while ($term = db_fetch_object($result)) {
-    taxonomy_del_term($term->tid);
+    taxonomy_del_term($term->tid, $did);
   }
 
+  $vocabulary['did'] = $did;
   module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
 
   cache_clear_all();
@@ -336,26 +346,6 @@ function taxonomy_del_vocabulary($vid) {
   return SAVED_DELETED;
 }
 
-function _taxonomy_confirm_del_vocabulary($vid) {
-  $vocabulary = taxonomy_get_vocabulary($vid);
-
-  $form['type'] = array('#type' => 'value', '#value' => 'vocabulary');
-  $form['vid'] = array('#type' => 'value', '#value' => $vid);
-  $form['name'] = array('#type' => 'value', '#value' => $vocabulary->name);
-  return confirm_form('taxonomy_vocabulary_confirm_delete', $form,
-                  t('Are you sure you want to delete the vocabulary %title?',
-                  array('%title' => theme('placeholder', $vocabulary->name))),
-                  'admin/taxonomy', t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'),
-                  t('Delete'),
-                  t('Cancel'));
-}
-
-function taxonomy_vocabulary_confirm_delete_submit($form_id, $form_values) {
-  $status = taxonomy_del_vocabulary($form_values['vid']);
-  drupal_set_message(t('Deleted vocabulary %name.', array('%name' => theme('placeholder', $form_values['name']))));
-  return 'admin/taxonomy';
-}
-
 function taxonomy_form_term($edit = array()) {
   $vocabulary_id = isset($edit['vid']) ? $edit['vid'] : arg(4);
   $vocabulary = taxonomy_get_vocabulary($vocabulary_id);
@@ -487,7 +477,14 @@ function taxonomy_save_term(&$edit) {
   return $status;
 }
 
-function taxonomy_del_term($tid) {
+function taxonomy_del_term($tid, $vocab_did = NULL) {
+  static $count;
+  if ($vocab_did) {
+    $did = $vocab_did;
+  }
+  else {
+    $did = next_delete_id();
+  }
   $tids = array($tid);
   while ($tids) {
     $children_tids = $orphans = array();
@@ -505,44 +502,45 @@ function taxonomy_del_term($tid) {
 
       $term = (array) taxonomy_get_term($tid);
 
-      db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
-      db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
-      db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
-      db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
-      db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
+      $message = t('Moved term %name and it\'s children to the %trash.', array('%name' => theme('placeholder', $term['name']), '%trash' => l(t('trash'), 'admin/trash')));
+
+      // Build initial package data if this is the beginning of package deletion
+      if (!$recurse && !$vocab_did) {
+        $preview = array('description' => $term['description']);
+        $trash_data = array('did' => $did, 'rid' => $term['vid'], 'name' => 'taxonomy term', 'url' => "admin/taxonomy/edit/term/$tid", 'description' => 'category term', 'title' => $term['name'], 'preview' => $preview, 'message' => $message);
+        $recurse = 1;
+      }
+
+      // Add to the package preview
+      else {
+        $count++;
+        $preview = array();
+        if ($term['name']) {
+          $preview['term'. $count] = $term['name'];
+        }
+        if ($term['description']) {
+          $preview['description'. $count] = $term['description'];
+        }
+        $trash_data = array('did' => $did, 'preview' => $preview);
+      }
+
+      system_trash($trash_data, 'DELETE FROM {term_data} WHERE tid = %d', $tid);
+      system_trash(array(), 'DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
+      system_trash(array(), 'DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
+      system_trash(array(), 'DELETE FROM {term_synonym} WHERE tid = %d', $tid);
+      system_trash(array(), 'DELETE FROM {term_node} WHERE tid = %d', $tid);
+      $term['did'] = $did;
 
       module_invoke_all('taxonomy', 'delete', 'term', $term);
     }
 
     $tids = $orphans;
   }
-
   cache_clear_all();
 
   return SAVED_DELETED;
 }
 
-function _taxonomy_confirm_del_term($tid) {
-  $term = taxonomy_get_term($tid);
-
-  $form['type'] = array('#type' => 'value', '#value' => 'term');
-  $form['name'] = array('#type' => 'value', '#value' => $term->name);
-  $form['tid'] = array('#type' => 'value', '#value' => $tid);
-  return confirm_form('taxonomy_term_confirm_delete', $form,
-                  t('Are you sure you want to delete the term %title?',
-                  array('%title' => theme('placeholder', $term->name))),
-                  'admin/taxonomy',
-                  t('Deleting a term will delete all its children if there are any. This action cannot be undone.'),
-                  t('Delete'),
-                  t('Cancel'));
-}
-
-function taxonomy_term_confirm_delete_submit($form_id, $form_values) {
-  taxonomy_del_term($form_values['tid']);
-  drupal_set_message(t('Deleted term %name.', array('%name' => theme('placeholder', $form_values['name']))));
-  return 'admin/taxonomy';
-}
-
 /**
  * Generate a form element for selecting terms from a vocabulary.
  */
@@ -729,7 +727,7 @@ function taxonomy_node_validate(&$node) 
  * Save term associations for a given node.
  */
 function taxonomy_node_save($nid, $terms) {
-  taxonomy_node_delete($nid);
+  db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
 
   // Free tagging vocabularies do not send their tids in the form,
   // so we'll detect them here and process them independently.
@@ -798,13 +796,6 @@ function taxonomy_node_save($nid, $terms
 }
 
 /**
- * Remove associations of a node to its terms.
- */
-function taxonomy_node_delete($nid) {
-  db_query('DELETE FROM {term_node} WHERE nid = %d', $nid);
-}
-
-/**
  * Find all term objects related to a given term ID.
  */
 function taxonomy_get_related($tid, $key = 'tid') {
@@ -1175,7 +1166,7 @@ function taxonomy_nodeapi($node, $op, $a
       taxonomy_node_save($node->nid, $node->taxonomy);
       break;
     case 'delete':
-      taxonomy_node_delete($node->nid);
+      system_trash(array(), 'DELETE FROM {term_node} WHERE nid = %d', $node->nid);
       break;
     case 'validate':
       taxonomy_node_validate($node);
@@ -1293,8 +1284,8 @@ function taxonomy_term_page($str_tids = 
  * Page to add or edit a vocabulary
  */
 function taxonomy_admin_vocabulary_edit($vid = NULL) {
-  if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
-    return _taxonomy_confirm_del_vocabulary($vid);
+  if ($_POST['op'] == t('Delete')) {
+    return taxonomy_del_vocabulary($vid);
   }
   elseif ($vid) {
     $vocabulary = (array)taxonomy_get_vocabulary($vid);
@@ -1306,8 +1297,8 @@ function taxonomy_admin_vocabulary_edit(
  * Page to list terms for a vocabulary
  */
 function taxonomy_admin_term_edit($tid = NULL) {
-  if ($_POST['op'] == t('Delete') || $_POST['edit']['confirm']) {
-    return _taxonomy_confirm_del_term($tid);
+  if ($_POST['op'] == t('Delete')) {
+    return taxonomy_del_term($tid);
   }
   elseif ($tid) {
     $term = (array)taxonomy_get_term($tid);
@@ -1320,10 +1311,12 @@ function taxonomy_admin_term_edit($tid =
  */
 function taxonomy_rss_item($node) {
   $output = array();
-  foreach ($node->taxonomy as $term) {
-    $output[] = array('key'   => 'category',
-                      'value' => check_plain($term->name),
-                      'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, NULL, NULL, TRUE)));
+  if (is_array($node->taxonomy)) {
+    foreach ($node->taxonomy as $term) {
+      $output[] = array('key'   => 'category',
+                        'value' => check_plain($term->name),
+                        'attributes' => array('domain' => url('taxonomy/term/'. $term->tid, NULL, NULL, TRUE)));
+    }
   }
   return $output;
 }
=== modified file 'modules/upload.module'
--- modules/upload.module	
+++ modules/upload.module	
@@ -647,6 +647,7 @@ function upload_save($node) {
 
 function upload_delete($node) {
   $files = array();
+
   $result = db_query('SELECT * FROM {files} WHERE nid = %d', $node->nid);
   while ($file = db_fetch_object($result)) {
     $files[$file->fid] = $file;
@@ -654,12 +655,11 @@ 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);
+    system_trash(array(), 'DELETE FROM {file_revisions} WHERE fid = %d', $fid);
   }
 
   // Delete all files associated with the node
-  db_query('DELETE FROM {files} WHERE nid = %d', $node->nid);
+  system_trash(array(), 'DELETE FROM {files} WHERE nid = %d', $node->nid);
 }
 
 function upload_delete_revision($node) {
@@ -670,14 +670,12 @@ function upload_delete_revision($node) {
 
       // 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);
+        system_trash(array(), 'DELETE FROM {files} WHERE fid = %d', $file->fid);
       }
     }
   }
-
   // delete the revision
-  db_query('DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
+  system_trash(array(), 'DELETE FROM {file_revisions} WHERE vid = %d', $node->vid);
 }
 
 function _upload_form($node) {
@@ -812,3 +810,105 @@ function upload_js() {
   print drupal_to_js(array('status' => TRUE, 'data' => $output));
   exit;
 }
+
+/**
+ * Implementation of hook_system_trash
+ *
+ * Composes metadata for file attachments related to nodes that are being sent to trash.
+ * Renames the files to make them unavailable.
+ *
+ * @param $trash_data
+ *   Metadata for the package being deleted.
+ * @return
+ *   An array with a 'files' element, which contains upload metadata for the attachments
+ *   to the node in question.
+ */
+function upload_system_trash($trash_data) {
+
+  $rid = $trash_data['rid'];
+
+  // Only invoke if it's a node being deleted, and nid is known
+  if (($trash_data['name'] == 'node') && isset($rid)) {
+    $node = node_load($rid);
+    $file_info = upload_load($node);
+
+    // Pull a random string for appending to the file name--this prevents direct access to the file
+    $key = substr(md5(uniqid(rand(), TRUE)), 0, 26);
+
+    // Add each attachment's filepatch to the metadata
+    foreach ($file_info as $fid => $file) {
+      $filepaths[$fid]['original_path'] = $file->filepath;
+      $filepaths[$fid]['renamed_path'] = $file->filepath . $key;
+      $filepaths[$fid]['original_name'] = $file->filename;
+      $filepaths[$fid]['renamed_name'] = $file->filename . $key;
+
+      //Rename file
+      if(!file_move(&$file->filepath, $filepaths[$fid]['renamed_path'], FILE_EXISTS_ERROR)) {
+        drupal_set_message(t('Attachment %file could not be renamed--original name kept', array('%file' => $filepaths[$fid]['original_name'])), 'error');
+        $filepaths[$fid]['renamed_path'] = $filepaths[$fid]['original_path'];
+        $filepaths[$fid]['renamed_name'] = $filepaths[$fid]['original_name'];
+      }
+    }
+
+    $recover['files'] = $filepaths;
+    return $recover;
+  }
+}
+
+/**
+ * Implementation of hook_system_trash_recover
+ *
+ * Processes upload metadata for the package being recovered. Specifically it restores the original file name
+ * to all node attachments
+ *
+ * @param $trash_data
+ *   Metadata for the package being recovered.
+ */
+function upload_system_trash_recover($trash_data) {
+
+  $rid = $trash_data['rid'];
+
+  // Only invoke if it's a node being recovered, and nid is known
+  if (($trash_data['name'] == 'node') && isset($rid)) {
+
+    $files = $trash_data['data']['files'];
+
+    if ($files) {
+      foreach ($files as $fid => $file) {
+
+        //Restore original filename
+        if(!file_move($file['renamed_path'], $file['original_path'])) {
+          drupal_set_message(t('Attachment %file could not be renamed to original name -- %name kept', array('%file' => $file['original_name'],
+            '%name' => $file['renamed_name'])), 'error');
+
+          // Can't restore, so update files table
+          db_query("UPDATE {files} SET filename = '%s', filepath = '%s' WHERE fid = %d", $file['renamed_name'],
+            $file['renamed_path'], $fid);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_system_trash_delete
+ *
+ * Permanently deletes any file attachments related to the package being deleted.
+ *
+ * @param $trash_data
+ *   Metadata for the package being deleted.
+ */
+function upload_system_trash_delete($trash_data) {
+
+  $files = $trash_data['data']['files'];
+
+  if ($files) {
+    foreach ($files as $fid => $filepath) {
+
+      // Check any other revisions pointing to file first.
+      if (db_result(db_query("SELECT COUNT(fid) FROM {file_revisions} WHERE fid = %d", $fid)) == 0) {
+        file_delete($filepath);
+      }
+    }
+  }
+}
=== modified file 'modules/user.module'
--- modules/user.module	
+++ modules/user.module	
@@ -1407,19 +1407,25 @@ function user_edit($category = 'account'
   $edit = $_POST['op'] ? $_POST['edit'] : (array)$account;
 
   if (arg(2) == 'delete') {
-    if ($edit['confirm']) {
-      db_query('DELETE FROM {users} WHERE uid = %d', $account->uid);
-      db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
-      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
-      db_query('DELETE FROM {authmap} WHERE uid = %d', $account->uid);
-      watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE);
-      drupal_set_message(t('The account has been deleted.'));
-      module_invoke_all('user', 'delete', $edit, $account);
-      drupal_goto('admin/user');
-    }
-    else {
-      return confirm_form('user_confirm_delete', array(), t('Are you sure you want to delete the account %name?', array('%name' => theme('placeholder', $account->name))), 'user/'. $account->uid, t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'), t('Delete'), t('Cancel'));
-    }
+    $did = next_delete_id();
+
+    // Build trash preview
+    $preview = array('email' => $account->mail);
+
+    $message = t('The account %account has been moved to the %trash--all submissions made by the user have been attributed to the anonymous user.', array('%account' => theme('placeholder', $account->name), '%trash' => l(t('trash'), 'admin/trash')));
+
+    db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
+
+    $trash_data = array('did' => $did, 'rid' => $account->uid, 'name' => 'user', 'url' => "user/$account->uid", 'description' => 'user account', 'title' => $account->name, 'preview' => $preview, 'message' => $message);
+    system_trash($trash_data, 'DELETE FROM {users} WHERE uid = %d', $account->uid);
+    system_trash(array(), 'DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
+    system_trash(array(), 'DELETE FROM {authmap} WHERE uid = %d', $account->uid);
+
+
+    watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE);
+    $account->did = $did;
+    module_invoke_all('user', 'delete', $edit, $account);
+    drupal_goto('admin/user');
   }
   else if ($_POST['op'] == t('Delete')) {
     if ($_REQUEST['destination']) {
@@ -1620,22 +1626,17 @@ function user_admin_access_add($mask = N
 function user_admin_access_delete($aid = 0) {
   $access_types = array('user' => t('username'), 'mail' => t('e-mail'));
   $edit = db_fetch_object(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
+  $did = next_delete_id();
 
-  $form = array();
-  $form['aid'] = array('#type' => 'hidden', '#value' => $aid);
-  $output = confirm_form('user_admin_access_delete_confirm', $form,
-                  t('Are you sure you want to delete the %type rule for %rule?', array('%type' => $access_types[$edit->type], '%rule' => theme('placeholder', $edit->mask))),
-                  'admin/access/rules',
-                  t('This action cannot be undone.'),
-                  t('Delete'),
-                  t('Cancel'));
-  return $output;
-}
+  // Build trash preview
+  $preview = array('rule' => $edit->mask);
+
+  $message = t('The access rule has been moved to the %trash.', array('%trash' => l(t('trash'), 'admin/trash')));
 
-function user_admin_access_delete_confirm_submit($form_id, $edit) {
-  db_query('DELETE FROM {access} WHERE aid = %d', $edit['aid']);
-  drupal_set_message(t('The access rule has been deleted.'));
-  return 'admin/access/rules';
+  $trash_data = array('did' => $did, 'rid' => $aid, 'name' => 'access rule', 'url' => "admin/access/rules/edit/$aid", 'description' => 'user access rule', 'title' => $access_types[$edit->type], 'preview' => $preview, 'message' => $message);
+  system_trash($trash_data, 'DELETE FROM {access} WHERE aid = %d', $aid);
+
+  drupal_goto('admin/access/rules');
 }
 
 /**
@@ -1875,8 +1876,14 @@ function user_admin_role() {
     }
   }
   else if ($op == t('Delete role')) {
-    db_query('DELETE FROM {role} WHERE rid = %d', $id);
-    db_query('DELETE FROM {permission} WHERE rid = %d', $id);
+    $did = next_delete_id();
+    $title = db_result(db_query('SELECT name FROM {role} WHERE rid = %d', $id));
+
+    $message = t('The role %role has been moved to the %trash.', array('%role' => theme('placeholder', $title), '%trash' => l(t('trash'), 'admin/trash')));
+
+    $trash_data = array('did' => $did, 'rid' => $id, 'name' => 'role', 'url' => "admin/access/roles/edit/$id", 'description' => 'user role', 'title' => $title, 'message' => $message);
+    system_trash($trash_data, 'DELETE FROM {role} WHERE rid = %d', $id);
+    system_trash(array(), 'DELETE FROM {permission} WHERE rid = %d', $id);
 
     // Update the users who have this role set:
     $result = db_query('SELECT DISTINCT(ur1.uid) FROM {users_roles} ur1 LEFT JOIN {users_roles} ur2 ON ur2.uid = ur1.uid WHERE ur1.rid = %d AND ur2.rid != ur1.rid', $id);
@@ -1887,10 +1894,9 @@ function user_admin_role() {
     }
 
     if ($uid) {
-      db_query('DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid));
+      system_trash(array(), 'DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid));
     }
 
-    drupal_set_message(t('The role has been deleted.'));
     drupal_goto('admin/access/roles');
   }
   else if ($op == t('Add role')) {
=== modified file 'update.php'
--- update.php	
+++ update.php	
@@ -362,6 +362,7 @@ function update_selection_page() {
 }
 
 function update_update_page() {
+
   // Set the installed version so updates start at the correct place.
   $_SESSION['update_remaining'] = array();
   foreach ($_POST['edit']['start'] as $module => $version) {
@@ -487,6 +488,10 @@ function update_progress_page_nojs() {
 }
 
 function update_finished_page($success) {
+
+  // The deleted table is cleared here to prevent stale data from being reinserted between db updates
+  db_query('DELETE FROM {deleted}');
+
   drupal_set_title('Drupal database update');
   // NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
   $links[] = '<a href="'. base_path() .'">main page</a>';
