Index: database/database.mysql =================================================================== RCS file: /cvs/drupal/drupal/database/database.mysql,v retrieving revision 1.201 diff -u -F^function -r1.201 database.mysql --- database/database.mysql 18 Oct 2005 14:41:26 -0000 1.201 +++ database/database.mysql 29 Oct 2005 12:51:35 -0000 @@ -239,6 +239,22 @@ ) TYPE=MyISAM; -- +-- Table structure for table 'deleted' +-- + +CREATE TABLE deleted ( + tid int(10) unsigned NOT NULL default '0', + did int(10) unsigned NOT NULL default '0', + rid varchar(255) NOT NULL default '', + uid int(10) unsigned NOT NULL default '0', + root_row varchar(255) NOT NULL default '', + data longtext NOT NULL, + preview longtext NOT NULL, + timestamp int(10) unsigned NOT NULL default '0', + PRIMARY KEY (tid) +) TYPE=MyISAM; + +-- -- Table structure for table 'files' -- Index: database/database.pgsql =================================================================== RCS file: /cvs/drupal/drupal/database/database.pgsql,v retrieving revision 1.140 diff -u -F^function -r1.140 database.pgsql --- database/database.pgsql 18 Oct 2005 14:41:26 -0000 1.140 +++ database/database.pgsql 29 Oct 2005 12:51:35 -0000 @@ -235,6 +235,22 @@ ); -- +-- Table structure for table 'deleted' +-- + +CREATE TABLE deleted ( + tid integer NOT NULL default '0', + did integer NOT NULL default '0', + rid text NOT NULL default '', + uid integer NOT NULL default '0', + root_row text NOT NULL default '', + data text NOT NULL default '', + preview text NOT NULL default '', + timestamp integer NOT NULL default '0', + PRIMARY KEY (tid) +); + +-- -- Table structure for table 'files' -- Index: database/updates.inc =================================================================== RCS file: /cvs/drupal/drupal/database/updates.inc,v retrieving revision 1.140 diff -u -F^function -r1.140 updates.inc --- database/updates.inc 22 Oct 2005 15:14:46 -0000 1.140 +++ database/updates.inc 29 Oct 2005 12:51:36 -0000 @@ -67,7 +67,8 @@ "2005-09-07" => "update_147", "2005-09-18" => "update_148", "2005-09-27" => "update_149", - "2005-10-15" => "update_150" + "2005-10-15" => "update_150", + "2005-10-27" => "update_151" ); function update_110() { @@ -919,6 +920,43 @@ function update_150() { return $ret; } +function update_151() { + $ret = array(); + + switch ($GLOBALS['db_type']) { + case 'mysqli': + case 'mysql': + $ret[] = update_sql("CREATE TABLE deleted ( + tid int(10) unsigned NOT NULL default '0', + did int(10) unsigned NOT NULL default '0', + rid varchar(255) NOT NULL default '', + uid int(10) unsigned NOT NULL default '0', + root_row varchar(255) NOT NULL default '', + data longtext NOT NULL, + preview longtext NOT NULL, + timestamp int(10) unsigned NOT NULL default '0', + PRIMARY KEY (tid) + )"); + break; + case 'pgsql': + $ret[] = update_sql("CREATE TABLE deleted ( + tid integer NOT NULL default '0', + did integer NOT NULL default '0', + rid text NOT NULL default '', + uid integer NOT NULL default '0', + root_row text NOT NULL default '', + data text NOT NULL, + preview text NOT NULL, + timestamp integer NOT NULL default '0', + PRIMARY KEY (tid) + )"); + break; + default: + break; + } + return $ret; +} + function update_sql($sql) { $edit = $_POST["edit"]; $result = db_query($sql); Index: includes/locale.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/locale.inc,v retrieving revision 1.56 diff -u -F^function -r1.56 locale.inc --- includes/locale.inc 21 Oct 2005 11:14:28 -0000 1.56 +++ includes/locale.inc 29 Oct 2005 12:51:37 -0000 @@ -935,10 +935,14 @@ function _locale_export_remove_plural($e } 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(); + $result = db_fetch_object(db_query('SELECT source, location FROM {locales_source} WHERE lid = %d', $lid)); + $preview = array('source' => $result->source, 'location' => $result->location); + drupal_set_message(t('The string has been moved to trash.')); + system_trash($did, $lid, 'locale', $preview, 'DELETE FROM {locales_source} WHERE lid = %d', $lid); + system_trash($did, $lid, NULL, NULL, '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'); } /** @@ -1166,7 +1170,7 @@ function _locale_prepare_iso_list() { function _locale_get_iso639_list() { return array( "aa" => array("Afar"), - "ab" => array("Abkhazian", "аҧÑ?уа бызшәа"), + "ab" => array("Abkhazian", "аҧÑ?уа бызшәа"), "ae" => array("Avestan"), "af" => array("Afrikaans"), "ak" => array("Akan"), @@ -1177,8 +1181,8 @@ function _locale_get_iso639_list() { "ay" => array("Aymara"), "az" => array("Azerbaijani", "azÉ™rbaycan"), "ba" => array("Bashkir"), - "be" => array("Belarusian", "БеларуÑ?каÑ?"), - "bg" => array("Bulgarian", "БългарÑ?ки"), + "be" => array("Belarusian", "БеларуÑ?каÑ?"), + "bg" => array("Bulgarian", "БългарÑ?ки"), "bh" => array("Bihari"), "bi" => array("Bislama"), "bm" => array("Bambara", "Bamanankan"), @@ -1199,14 +1203,14 @@ function _locale_get_iso639_list() { "de" => array("German", "Deutsch"), "dv" => array("Maldivian"), "dz" => array("Bhutani"), - "ee" => array("Ewe", "Æ?Ê‹É›"), + "ee" => array("Ewe", "Æ?Ê‹É›"), "el" => array("Greek", "Ελληνικά"), "en" => array("English"), "eo" => array("Esperanto"), "es" => array("Spanish", "Español"), "et" => array("Estonian", "Eesti"), "eu" => array("Basque", "Euskera"), - "fa" => array("Persian", "Ù?ارسی"), + "fa" => array("Persian", "Ù?ارسی"), "ff" => array("Fulah", "Fulfulde"), "fi" => array("Finnish", "Suomi"), "fj" => array("Fiji"), @@ -1221,7 +1225,7 @@ function _locale_get_iso639_list() { "gv" => array("Manx"), "ha" => array("Hausa"), "he" => array("Hebrew", "עברית"), - "hi" => array("Hindi", "हिनà¥?दी"), + "hi" => array("Hindi", "हिनà¥?दी"), "ho" => array("Hiri Motu"), "hr" => array("Croatian", "Hrvatski"), "hu" => array("Hungarian", "Magyar"), @@ -1232,7 +1236,7 @@ function _locale_get_iso639_list() { "ie" => array("Interlingue"), "ig" => array("Igbo"), "ik" => array("Inupiak"), - "is" => array("Icelandic", "Ã?slenska"), + "is" => array("Icelandic", "Ã?slenska"), "it" => array("Italian", "Italiano"), "iu" => array("Inuktitut"), "ja" => array("Japanese", "日本語"), @@ -1244,7 +1248,7 @@ function _locale_get_iso639_list() { "kk" => array("Kazakh", "Қазақ"), "kl" => array("Greenlandic"), "km" => array("Cambodian"), - "kn" => array("Kannada", "ಕನà³?ನಡ"), + "kn" => array("Kannada", "ಕನà³?ನಡ"), "ko" => array("Korean", "한국어"), "kr" => array("Kanuri"), "ks" => array("Kashmiri"), @@ -1262,7 +1266,7 @@ function _locale_get_iso639_list() { "mg" => array("Malagasy"), "mh" => array("Marshallese"), "mi" => array("Maori"), - "mk" => array("Macedonian", "МакедонÑ?ки"), + "mk" => array("Macedonian", "МакедонÑ?ки"), "ml" => array("Malayalam", "മലയാളം"), "mn" => array("Mongolian"), "mo" => array("Moldavian"), @@ -1292,7 +1296,7 @@ function _locale_get_iso639_list() { "rm" => array("Rhaeto-Romance"), "rn" => array("Kirundi"), "ro" => array("Romanian", "Română"), - "ru" => array("Russian", "РуÑ?Ñ?кий"), + "ru" => array("Russian", "РуÑ?Ñ?кий"), "rw" => array("Kinyarwanda"), "sa" => array("Sanskrit"), "sc" => array("Sardinian"), @@ -1301,20 +1305,20 @@ function _locale_get_iso639_list() { "sg" => array("Sango"), "sh" => array("Serbo-Croatian"), "si" => array("Singhalese"), - "sk" => array("Slovak", "SlovenÄ?ina"), - "sl" => array("Slovenian", "SlovenÅ¡Ä?ina"), + "sk" => array("Slovak", "SlovenÄ?ina"), + "sl" => array("Slovenian", "SlovenÅ¡Ä?ina"), "sm" => array("Samoan"), "sn" => array("Shona"), "so" => array("Somali"), "sq" => array("Albanian", "Shqip"), - "sr" => array("Serbian", "СрпÑ?ки"), + "sr" => array("Serbian", "СрпÑ?ки"), "ss" => array("Siswati"), "st" => array("Sesotho"), "su" => array("Sudanese"), "sv" => array("Swedish", "Svenska"), "sw" => array("Swahili", "Kiswahili"), - "ta" => array("Tamil", "தமிழà¯?"), - "te" => array("Telugu", "తెలà±?à°—à±?"), + "ta" => array("Tamil", "தமிழà¯?"), + "te" => array("Telugu", "తెలà±?à°—à±?"), "tg" => array("Tajik"), "th" => array("Thai", "ภาษาไทย"), "ti" => array("Tigrinya"), @@ -1328,7 +1332,7 @@ function _locale_get_iso639_list() { "tw" => array("Twi"), "ty" => array("Tahitian"), "ug" => array("Uighur"), - "uk" => array("Ukrainian", "УкраїнÑ?ька"), + "uk" => array("Ukrainian", "УкраїнÑ?ька"), "ur" => array("Urdu", "اردو"), "uz" => array("Uzbek", "o'zbek"), "ve" => array("Venda"), @@ -1340,7 +1344,7 @@ function _locale_get_iso639_list() { "yo" => array("Yoruba", "Yorùbá"), "za" => array("Zhuang"), "zh-hans" => array("Chinese, Simplified", "简体中文"), - "zh-hant" => array("Chinese, Traditional", "ç¹?體中文"), + "zh-hant" => array("Chinese, Traditional", "ç¹?體中文"), "zu" => array("Zulu", "isiZulu"), ); } Index: modules/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block.module,v retrieving revision 1.184 diff -u -F^function -r1.184 block.module --- modules/block.module 22 Oct 2005 15:14:46 -0000 1.184 +++ modules/block.module 29 Oct 2005 12:51:38 -0000 @@ -408,10 +408,12 @@ function block_box_add() { */ 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); - - return confirm_form('block_box_delete_confirm', $form, t('Are you sure you want to delete the block %name?', array('%name' => theme('placeholder', $info))), 'admin/block', '', t('Delete'), t('Cancel')); + $did = next_delete_id(); + $preview = array('title' => $box['title'], 'info' => $box['info'], 'body' => $box['body']); + drupal_set_message(t('The block %name has been moved to trash.', array('%name' => theme('placeholder', $box['info'])))); + system_trash($did, $bid, 'block', $preview, 'DELETE FROM {boxes} WHERE bid = %d', $bid); + cache_clear_all(); + drupal_goto('admin/block'); } /** @@ -419,10 +421,7 @@ function block_box_delete($bid = 0) { */ function block_box_delete_confirm_execute($form_id, $edit) { $form = $GLOBALS['form_values']; - db_query('DELETE FROM {boxes} WHERE bid = %d', $form['bid']); - drupal_set_message(t('The block %name has been removed.', array('%name' => theme('placeholder', $form['info'])))); - cache_clear_all(); - drupal_goto('admin/block'); + }; Index: modules/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book.module,v retrieving revision 1.326 diff -u -F^function -r1.326 book.module --- modules/book.module 28 Oct 2005 14:04:20 -0000 1.326 +++ modules/book.module 29 Oct 2005 12:51:40 -0000 @@ -217,7 +217,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($node->did, $node->nid, NULL, NULL, 'DELETE FROM {book} WHERE nid = %d', $node->nid); } /** @@ -298,8 +298,10 @@ function book_outline() { 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(); + $preview = array('title' => $node-title, 'body' => $node->body); + drupal_set_message(t('The post has been removed from the book and placed in trash.')); + system_trash($did, $node->nid, 'book', $preview, 'DELETE FROM {book} WHERE nid = %d', $node->nid); drupal_goto("node/$node->nid"); break; Index: modules/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment.module,v retrieving revision 1.384 diff -u -F^function -r1.384 comment.module --- modules/comment.module 20 Oct 2005 09:27:36 -0000 1.384 +++ modules/comment.module 29 Oct 2005 12:51:41 -0000 @@ -232,8 +232,14 @@ 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); + $result = db_query('SELECT * FROM {comments} WHERE nid = %d', $node->nid); + while ($comment = db_fetch_object($result)) { + $i++; + $preview['subject'. $i] = $comment->subject; + $preview['comment'. $i] = $comment->comment; + } + system_trash($node->did, $node->nid, NULL, $preview, 'DELETE FROM {comments} WHERE nid = %d', $node->nid); + system_trash($node->did, $node->nid, NULL, NULL, 'DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid); break; case 'update index': @@ -909,12 +915,10 @@ 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 ($comment->cid && $_POST['edit']['confirm']) { - drupal_set_message(t('The comment and all its replies have been deleted.')); + if ($comment->cid) { + drupal_set_message(t('The comment and all its replies have been moved to trash.')); // Delete comment and its replies. _comment_delete_thread($comment); @@ -926,20 +930,10 @@ function comment_delete($cid) { drupal_goto("node/$comment->nid"); } - else if ($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; } /** @@ -1289,9 +1283,19 @@ function theme_comment_post_forbidden() } function _comment_delete_thread($comment) { + static $did; + if ($did) { + $root_row = NULL; + $preview = NULL; + } + else { + $did = next_delete_id(); + $root_row = 'comment'; + $preview = array('subject' => $comment->subject, 'comment' => $comment->comment); + } // 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($did, $comment->nid, $root_row, $preview, 'DELETE FROM {comments} WHERE cid = %d', $comment->cid); + watchdog('content', t('Comment: moved %subject to trash.', array('%subject' => theme('placeholder', $comment->subject)))); comment_invoke_comment($comment, 'delete'); Index: modules/contact.module =================================================================== RCS file: /cvs/drupal/drupal/modules/contact.module,v retrieving revision 1.26 diff -u -F^function -r1.26 contact.module --- modules/contact.module 11 Oct 2005 19:44:34 -0000 1.26 +++ modules/contact.module 29 Oct 2005 12:51:41 -0000 @@ -197,18 +197,11 @@ function contact_admin_edit($category = } function contact_admin_delete($category) { - if ($_POST['op'] != t('Delete')) { - return confirm_form('contact_admin_delete', array(), - t('Are you sure you want to delete %category?', array('%category' => theme('placeholder', $category))), - 'admin/contact', - t('This action cannot be undone.'), - t('Delete'), - t('Cancel')); - } - else { - db_query("DELETE FROM {contact} WHERE category = '%s'", $category); + $did = next_delete_id(); + $preview = array('category' => $category); + drupal_set_message(t('Contact category %category moved to trash', array('%category' => t($category)))); + system_trash($did, $category, 'contact', $preview, "DELETE FROM {contact} WHERE category = '%s'", $category); drupal_goto('admin/contact'); - } } Index: modules/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter.module,v retrieving revision 1.77 diff -u -F^function -r1.77 filter.module --- modules/filter.module 22 Oct 2005 15:14:46 -0000 1.77 +++ modules/filter.module 29 Oct 2005 12:51:43 -0000 @@ -339,33 +339,23 @@ function filter_admin_add() { * Menu callback; confirm deletion of a format. */ function filter_admin_delete() { - $edit = $_POST['edit']; - if ($edit['confirm']) { - if ($edit['format'] != variable_get('filter_default_format', 1)) { - db_query("DELETE FROM {filter_formats} WHERE format = %d", $edit['format']); - db_query("DELETE FROM {filters} WHERE format = %d", $edit['format']); - - $default = variable_get('filter_default_format', 1); - db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $edit['format']); - db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $edit['format']); - db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $edit['format']); - - cache_clear_all('filter:'. $edit['format'], true); - - drupal_set_message(t('Deleted input format %format.', array('%format' => theme('placeholder', $edit['name'])))); - } - drupal_goto('admin/filters'); - } - $format = arg(3); - $format = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); - - $form['format'] = array('#type' => 'hidden', '#value' => $format->format); - $form['name'] = array('#type' => 'hidden', '#value' => $format->name); - - return confirm_form('filter_admin_delete', $form, t('Are you sure you want to delete the input format %format?', array('%format' => theme('placeholder', $format->name))), 'admin/filters', t('If you have any content left in this input format, it will be switched to the default input format. This action cannot be undone.'), t('Delete'), t('Cancel')); - + if ($format != variable_get('filter_default_format', 1)) { + $result = db_fetch_object(db_query('SELECT * FROM {filter_formats} WHERE format = %d', $format)); + $did = next_delete_id(); + $preview = array('name' => $result->name); + drupal_set_message(t('Input format %format sent to trash.', array('%format' => theme('placeholder', $result->name)))); + system_trash($did, $format, 'filter', $preview, "DELETE FROM {filters} WHERE format = %d", $format); + system_trash($did, $format, NULL, NULL, "DELETE FROM {filter_formats} WHERE format = %d", $format); + + $default = variable_get('filter_default_format', 1); + db_query("UPDATE {node_revisions} SET format = %d WHERE format = %d", $default, $format); + db_query("UPDATE {comments} SET format = %d WHERE format = %d", $default, $format); + db_query("UPDATE {boxes} SET format = %d WHERE format = %d", $default, $format); + cache_clear_all('filter:'. $format, true); + } + drupal_goto('admin/filters'); } /** Index: modules/forum.module =================================================================== RCS file: /cvs/drupal/drupal/modules/forum.module,v retrieving revision 1.280 diff -u -F^function -r1.280 forum.module --- modules/forum.module 28 Oct 2005 13:56:26 -0000 1.280 +++ modules/forum.module 29 Oct 2005 12:51:44 -0000 @@ -86,14 +86,8 @@ function forum_admin() { } break; case t('Delete'): - if (!$edit['confirm']) { - $output = _forum_confirm_delete($edit['tid']); - break; - } - else { $name = $edit['name']; $edit['name'] = 0; - } case t('Submit'): $status = taxonomy_save_term($edit); if (arg(3) == 'container') { @@ -139,10 +133,10 @@ function forum_admin() { function forum_taxonomy($op, $type, $object = NULL) { if ($op == 'delete' && $type == 'term' && $object->vid == _forum_get_vid()) { $results = db_query('SELECT f.nid FROM {forum} f WHERE f.tid = %d', $object->tid); - while ($node = db_fetch_object($results)) { - $edit['nid'] = $node->nid; - $edit['confirm'] = TRUE; - node_delete($edit); + while ($nid = db_fetch_object($results)) { + $node = node_load($nid->nid); + $node->did = $object->did; + node_delete($node); } } elseif ($op == 'delete' && $type == 'vocabulary' && $object->vid == _forum_get_vid()) { @@ -151,20 +145,6 @@ function forum_taxonomy($op, $type, $obj } /** - * Returns a confirmation page for deleting a forum taxonomy term - * - * @param $tid ID of the term to be deleted - */ -function _forum_confirm_delete($tid) { - $term = taxonomy_get_term($tid); - - $form['tid'] = array('#type' => 'hidden', '#value' => $tid); - - 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 as well. This action cannot be undone.'), t('Delete'), t('Cancel')); -} - -/** * Returns a form for adding a container to the forum vocabulary * * @param $edit Associative array containing a container term to be added or edited. @@ -580,7 +560,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($node->did, $node->nid, NULL, NULL, 'DELETE FROM {forum} WHERE nid = %d', $node->nid); } /** Index: modules/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale.module,v retrieving revision 1.128 diff -u -F^function -r1.128 locale.module --- modules/locale.module 13 Oct 2005 10:02:31 -0000 1.128 +++ modules/locale.module 29 Oct 2005 12:51:44 -0000 @@ -294,39 +294,28 @@ function locale_admin_manage() { function locale_admin_manage_delete_screen() { include_once './includes/locale.inc'; $langcode = arg(4); - $edit = $_POST['edit']; - - // Check confirmation and if so, delete language - if ($edit['confirm']) { - $languages = locale_supported_languages(FALSE, TRUE); - if (isset($languages['name'][$edit['langcode']])) { - db_query("DELETE FROM {locales_meta} WHERE locale = '%s'", $edit['langcode']); - db_query("DELETE FROM {locales_target} WHERE locale = '%s'", $edit['langcode']); - $message = t('The language %locale has been removed.', array('%locale' => theme('placeholder', t($languages['name'][$edit['langcode']])))); - drupal_set_message($message); - watchdog('locale', $message); - } - - // Changing the locale settings impacts the interface: - cache_clear_all(); - drupal_goto('admin/locale/language/overview'); - } - + // Do not allow deletion of English locale if ($langcode == 'en') { drupal_goto('admin/locale/language/overview'); return; } - // For other locales, warn user that data loss is ahead + // Delete language $languages = locale_supported_languages(FALSE, TRUE); + if (isset($languages['name'][$langcode])) { + $did = next_delete_id(); + $preview = array('language code' => $langcode, 'name' => $languages['name'][$langcode]); + $message = t('The language %locale has been sent to trash.', array('%locale' => theme('placeholder', t($languages['name'][$langcode])))); + drupal_set_message($message); + watchdog('locale', $message); + system_trash($did, $langcode, 'locale', $preview, "DELETE FROM {locales_meta} WHERE locale = '%s'", $langcode); + system_trash($did, $langcode, NULL, NULL, "DELETE FROM {locales_target} WHERE locale = '%s'", $langcode); + } - $form['langcode'] = array('#type' => 'hidden', '#value' => $langcode); - return confirm_form('locale_admin_manage_delete_screen', $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')); + // Changing the locale settings impacts the interface: + cache_clear_all(); + drupal_goto('admin/locale/language/overview'); } /** Index: modules/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node.module,v retrieving revision 1.543 diff -u -F^function -r1.543 node.module --- modules/node.module 28 Oct 2005 14:04:20 -0000 1.543 +++ modules/node.module 29 Oct 2005 12:51:47 -0000 @@ -866,6 +866,9 @@ function node_menu($may_cache) { 'access' => user_access('administer nodes')); $items[] = array('path' => 'admin/node/action', 'title' => t('content'), 'type' => MENU_CALLBACK); + $items[] = array('path' => 'admin/node/batch_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); $items[] = array('path' => 'admin/settings/node', 'title' => t('posts'), @@ -907,7 +910,7 @@ function node_menu($may_cache) { 'weight' => 1, 'type' => MENU_LOCAL_TASK); $items[] = array('path' => 'node/'. arg(1) .'/delete', 'title' => t('delete'), - 'callback' => 'node_delete_page', + 'callback' => 'node_delete_confirm', 'access' => node_access('delete', $node), 'weight' => 1, 'type' => MENU_CALLBACK); @@ -1100,18 +1103,20 @@ function node_filter_form_execute() { * Generate the content administration overview. */ function node_admin_nodes_execute($form_id, $edit) { - $operations = node_operations(); - if ($operations[$edit['operation']][1]) { - // Flag changes - $operation = $operations[$edit['operation']][1]; - foreach ($edit['nodes'] as $nid => $value) { - if ($value) { - db_query($operation, $nid); + if ($edit['operation'] != 'delete') { + $operations = node_operations(); + if ($operations[$edit['operation']][1]) { + // Flag changes + $operation = $operations[$edit['operation']][1]; + foreach ($edit['nodes'] as $nid => $value) { + if ($value) { + db_query($operation, $nid); + } } + drupal_set_message(t('The update has been performed.')); } - drupal_set_message(t('The update has been performed.')); + drupal_goto('admin/node'); } - drupal_goto('admin/node'); } function node_admin_nodes_validate($form_id, $edit) { @@ -1160,7 +1165,7 @@ function node_admin_nodes() { // If you are attempting to delete nodes, display the multiple deletion form. if ($form_values['operation'] == 'delete') { - $output = node_multiple_delete_form(); + $output = node_multiple_delete_confirm(); } return $output; @@ -1197,30 +1202,82 @@ function theme_node_admin_nodes($form) { return $output; } -function node_multiple_delete_form() { +function node_multiple_delete_confirm() { global $form_values; - $form['nodes'] = array('#prefix' => ''); - foreach ($form_values['nodes'] as $nid => $value) { - if ($value) { - $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid)); - $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#tree' => TRUE, '#prefix' => '
  • ', '#suffix' => check_plain($title) .'
  • '); + $edit = $_POST['edit']; + // Grab nids from url for execute + if ($edit['confirm']) { + $nids = array_keys($edit['checkboxes']); + } + else { + // Keep only checked nodes' nids + $nids = array_keys(array_intersect($form_values['nodes'], array('1'))); + } + foreach ($nids as $nid) { + $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. + if (!in_array(FALSE, $extras)) { + $form['nodes'][$node->nid] = array('#type' => 'value', '#value' => 1); + $form['title'][$node->nid] = array('#type' => 'markup', '#value' => check_plain($node->title)); + $form['extras'][$node->nid] = $extras; + $options[$node->nid] = ''; + } } } - $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); - - return confirm_form('node_multiple_delete_form', $form, - t('Are you sure you want to delete these items?'), - 'admin/node', t('This action cannot be undone.'), - t('Delete all'), t('Cancel')); + if ($edit['confirm']) { + confirm_form('node_multiple_delete_confirm', $form, + t('The following items need to be reviewed'), + 'admin/node', t('Selected items will be sent to trash.'), + t('Delete selected'), t('Cancel')); + } + // Back to the admin page if no nodes can be deleted. + if (!count($options)) { + drupal_goto('admin/node'); + } + else { + $form['nodes']['#tree'] = TRUE; + $form['title']['#tree'] = TRUE; + $form['extras']['#tree'] = TRUE; + $form['checkboxes'] = array('#type' => 'checkboxes', '#options' => $options); + $form['confirm'] = array('#type' => 'value', '#value' => 1); + $form['operation'] = array('#type' => 'value', '#value' => 'delete'); + $form['#method'] = 'post'; + $form['#action'] = url('admin/node/batch_delete'); + return confirm_form('node_multiple_delete_confirm', $form, + t('The following items need to be reviewed'), + 'admin/node', t('Selected items will be sent to trash.'), + t('Delete selected'), t('Cancel')); + } } +function theme_node_multiple_delete_confirm($form) { + $rows = array(); + $header = array(''); + $output = ''; + foreach(element_children($form['nodes']) as $nid) { + $rows[] = array('
    '. ''. form_render($form['title'][$nid]) .''. form_render($form['checkboxes'][$nid]) .'
    '. form_render($form['extras'][$nid]) . form_render($form['nodes'][$nid])); + $rows[] = array(''); + } + $output .= theme('table', $header, $rows); + $output .= form_render($form); + return $output; +} -function node_multiple_delete_form_execute($form_id, $edit) { - if ($edit['confirm']) { - foreach ($edit['nodes'] as $nid => $value) { - node_delete(array('nid' => $nid, 'confirm' => 1)); +function node_multiple_delete_confirm_execute($form_id, $form_values) { + $edit = $_POST['edit']; + if ($form_values['confirm']) { + foreach ($edit['checkboxes'] as $nid => $value) { + // Merge extra confirm data with node + $node = object2array(node_load($nid)); + $node = array2object(array_merge($node, $edit['extras'][$nid])); + node_delete($node); } - drupal_set_message(t('The items have been deleted.')); } drupal_goto('admin/node'); } @@ -1334,11 +1391,14 @@ function node_revision_delete($nid, $rev $count_revisions = db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)); // Don't delete the last revision of the node or the current revision if ($count_revisions > 1) { - db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); - drupal_set_message(t('Deleted revision with the ID %revision.', array('%revision' => theme('placeholder', $revision)))); + $node = node_load($nid); + $did = next_delete_id(); + $preview = array('title' => $node->title, 'body' => $node->body); + drupal_set_message(t('Revision ID %revision moved to trash.', array('%revision' => theme('placeholder', $revision)))); + system_trash($did, $nid, 'node revision', $preview, "DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); } else { - drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); + drupal_set_message(t('Deletion failed. You tried to move the current revision to trash.')); } drupal_goto("node/$nid/revisions"); @@ -1842,29 +1902,73 @@ function node_form_execute($form_id, $ed /** * Ask for confirmation, and delete the node. */ -function node_delete($edit) { +function node_delete_confirm() { + $edit = $_POST['edit']; + $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1); $node = node_load($edit['nid']); - if (node_access('delete', $node)) { - $form['nid'] = array('#type' => 'hidden', '#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') ); + $form['nid'] = array('#type' => 'value', '#value' => $node->nid); + $form['confirm'] = array('#type' => 'value', '#value' => 1); + // Check for extra info from modules + $extras = node_invoke_nodeapi($node, 'delete pre'); + $form['extras'] = $extras; + if ($edit['confirm']) { + 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 send the + item to trash'), t('Delete'), t('Cancel')); + } + // 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_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 send the + item to trash'), t('Delete'), t('Cancel')); + } + } } - - return $output; } -function node_delete_confirm_execute() { - global $form_values; - $node = node_load($form_values['nid']); - - if (node_access('delete', $node)) { +function node_delete_confirm_execute($form_id, $form_values) { + if ($form_values['confirm']) { + // Merge extra confirm data with node + $node = object2array(node_load($form_values['nid'])); + $node = array2object(array_merge($node, $form_values)); + node_delete($node); + drupal_goto('node'); + } +} - if ($form_values['confirm']) { - db_query('DELETE FROM {node} WHERE nid = %d', $node->nid); - db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); +function node_delete($node) { + if (is_numeric($node)) { + $node = node_load($node); + } + elseif (is_array($node)) { + $node = node_load($node['nid']); + } + if (!$node->did) { + $node->did = next_delete_id(); + $root_row = 'node'; + } + else { + $root_row = NULL; + } + $preview['title'] = $node->title; + if ($node->body) { + $preview['body'] = $node->body; + } + drupal_set_message(t('%title moved to trash.', array('%title' => theme('placeholder', $node->title)))); + system_trash($node->did, $node->nid, $root_row, $preview, 'DELETE FROM {node} WHERE nid = %d', $node->nid); + system_trash($node->did, $node->nid, NULL, NULL, 'DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); // Call the node-specific callback (if any): node_invoke($node, 'delete'); @@ -1877,11 +1981,7 @@ function node_delete_confirm_execute() { 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)))); - } - } - drupal_goto('node'); } /** @@ -2003,20 +2103,6 @@ function node_page() { } /** - * Menu callback; the page for deleting a single node. - */ -function node_delete_page() { - $edit = $_POST['edit']; - $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1); - $node = node_load($edit['nid']); - if (!($output = node_delete($edit))) { - drupal_set_message(t('%title has been deleted.', array('%title' => theme('placeholder', $node->title)))); - drupal_goto(''); - } - return $output; -} - -/** * Implementation of hook_update_index(). */ function node_update_index() { Index: modules/path.module =================================================================== RCS file: /cvs/drupal/drupal/modules/path.module,v retrieving revision 1.68 diff -u -F^function -r1.68 path.module --- modules/path.module 28 Oct 2005 14:04:20 -0000 1.68 +++ modules/path.module 29 Oct 2005 12:51:47 -0000 @@ -105,17 +105,25 @@ function path_admin_edit($pid = 0) { * Menu callback; handles deletion of 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.')); + $did = next_delete_id(); + $result = db_fetch_object(db_query('SELECT src, dst FROM {url_alias} WHERE pid = %d', $pid)); + $preview = array('source' => $result->src, 'destination' => $result->dst); + drupal_set_message(t('The alias has been moved to trash.')); + system_trash($did, $pid, 'url alias', $preview, 'DELETE FROM {url_alias} WHERE pid = %d', $pid); drupal_goto('admin/path'); } /** * 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($node->did, $node->nid, NULL, NULL, "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) { @@ -222,7 +230,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; } Index: modules/poll.module =================================================================== RCS file: /cvs/drupal/drupal/modules/poll.module,v retrieving revision 1.173 diff -u -F^function -r1.173 poll.module --- modules/poll.module 11 Oct 2005 19:44:35 -0000 1.173 +++ modules/poll.module 29 Oct 2005 12:51:47 -0000 @@ -83,9 +83,16 @@ 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); +function poll_delete(&$node) { + // Poll trash preview + if ($node->choice) { + foreach ($node->choice as $key => $array) { + $c++; + $preview["choice$c"] = $array['chtext']; + } + } + system_trash($node->did, $node->nid, NULL, $preview, "DELETE FROM {poll} WHERE nid = %d", $node->nid); + system_trash($node->did, $node->nid, NULL, NULL, "DELETE FROM {poll_choices} WHERE nid = %d", $node->nid); } /** Index: modules/profile.module =================================================================== RCS file: /cvs/drupal/drupal/modules/profile.module,v retrieving revision 1.115 diff -u -F^function -r1.115 profile.module --- modules/profile.module 21 Oct 2005 11:14:55 -0000 1.115 +++ modules/profile.module 29 Oct 2005 12:51:48 -0000 @@ -533,18 +533,13 @@ function profile_admin_edit($fid) { * Menu callback; deletes a field from all user profiles. */ function profile_admin_delete($fid) { - $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid)); - if ($_POST['edit']['confirm']) { - db_query('DELETE FROM {profile_fields} WHERE fid = %d', $fid); - cache_clear_all(); - drupal_set_message(t('The field %field has been deleted.', array('%field' => theme('placeholder', $field->title)))); - drupal_goto('admin/settings/profile'); - } - else { - return confirm_form('profile_confirm_delete', $form, t('Do you want to remove the field %field?', - array('%field' => $field->title)), - 'admin/settings/profile', '', t('Delete'), t('Cancel')); - } + $did = next_delete_id(); + $result = db_fetch_object(db_query("SELECT title, explanation, category FROM {profile_fields} WHERE fid = %d", $fid)); + $preview = array('title' => $result->title, 'explanation' => $result->explanation, 'category' => $result->category); + drupal_set_message(t('The field %field has been moved to trash.', array('%field' => theme('placeholder', $result->title)))); + system_trash($did, $fid, 'profile', $preview, 'DELETE FROM {profile_fields} WHERE fid = %d', $fid); + cache_clear_all(); + drupal_goto('admin/settings/profile'); } function _profile_field_form($type, $edit = array()) { Index: modules/statistics.module =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics.module,v retrieving revision 1.210 diff -u -F^function -r1.210 statistics.module --- modules/statistics.module 12 Oct 2005 01:00:24 -0000 1.210 +++ modules/statistics.module 29 Oct 2005 12:51:49 -0000 @@ -474,7 +474,7 @@ function statistics_nodeapi(&$node, $op, switch ($op) { case 'delete': // clean up statistics table when node is deleted - db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid); + system_trash($node->did, $node->nid, NULL, NULL, 'DELETE FROM {node_counter} WHERE nid = %d', $node->nid); } } Index: modules/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system.module,v retrieving revision 1.247 diff -u -F^function -r1.247 system.module --- modules/system.module 26 Oct 2005 01:24:09 -0000 1.247 +++ modules/system.module 29 Oct 2005 12:51:50 -0000 @@ -46,7 +46,7 @@ function system_help($section) { * Implementation of hook_perm(). */ function system_perm() { - return array('administer site configuration', 'access administration pages', 'bypass input data check'); + return array('administer site configuration', 'access administration pages', 'bypass input data check', 'administer trash'); } /** @@ -138,6 +138,18 @@ 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' => TRUE); + $items[] = array('path' => 'trash/preview', 'title' => t('preview'), + 'callback' => 'system_trash_preview', 'access' => TRUE, 'type' => MENU_CALLBACK); + $items[] = array('path' => 'trash/recover', 'title' => t('recover'), + 'callback' => 'system_trash_recover', 'access' => TRUE, 'type' => MENU_CALLBACK); + $items[] = array('path' => 'trash/delete', 'title' => t('delete'), + 'callback' => 'system_trash_delete', 'access' => TRUE, 'type' => MENU_CALLBACK); + $items[] = array('path' => 'admin/trash/action', 'title' => t('content'), + 'type' => MENU_CALLBACK); } return $items; @@ -1231,3 +1243,256 @@ function confirm_form($form_id, $form, $ $form['actions']['cancel'] = array('#value' => l($no ? $no : t('Cancel'), $path)); return drupal_get_form($form_id, $form, 'confirm_form'); } + +function system_admin_trash() { + + global $user; + global $form_values; + $op = $_POST['op']; + $form = array(); + $view_all = user_access('administer trash'); + if ($view_all) { + $result = pager_query("SELECT * FROM {deleted} WHERE root_row != '' ORDER BY timestamp DESC", 25 , 0); + } + else { + $result = pager_query("SELECT * FROM {deleted} WHERE root_row != '' AND uid = %d ORDER BY timestamp DESC", 25 , 0, NULL, $user->uid); + } + $destination = drupal_get_destination(); + while ($recover = db_fetch_object($result)) { + $preview = unserialize($recover->preview); + $title = is_array($preview[0]) ? array_shift($preview[0]) : '[none]'; + $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->root_row)); + $form['title'][$recover->did] = array('#type' => 'markup', '#value' => t($title)); + $form['preview'][$recover->did] = array('#type' => 'markup', '#value' => l(t('preview'), "trash/preview/$recover->tid", array(), $destination)); + } + $form['did'] = array('#type' => 'checkboxes', '#options' => $items); + $form['pager'] = array('#value' => theme('pager', NULL, 25, 0)); + $form['buttons'] = array('#prefix' => '
    ', '#suffix' => '
    '); + $form['buttons']['recover'] = array('#type' => 'submit', '#value' => t('Recover')); + if ($view_all) { + $form['buttons']['delete'] = array('#type' => 'button', '#button_type' => 'submit', '#value' => t('Delete selected')); + $form['buttons']['delete_all'] = array('#type' => 'button', '#button_type' => 'submit', '#value' => t('Delete all')); + } + $form['buttons']['all'] = array('#type' => 'hidden', '#value' => 'all'); + $form['#method'] = 'post'; + $form['#action'] = url('admin/trash/action'); + + if ($op != t('Delete')) { + $output .= drupal_get_form('system_admin_trash', $form, 'system_trash_recover_delete'); + } + + // If you are attempting to delete nodes, display the multiple deletion form. + if ($op && ($op != t('Recover'))) { + $output = system_trash_multiple_delete_confirm(); + } + return $output; +} + +function theme_system_admin_trash($form) { + + // Overview table: + $output = form_render($form['buttons']); + $header = array(NULL, t('Title'), t('Type'), t('Trashed'), t('Operations')); + if (is_array($form['datetime'])) { + foreach (element_children($form['datetime']) as $key) { + $row = array(); + $row[] = form_render($form['did'][$key]); + $row[] = form_render($form['title'][$key]); + $row[] = form_render($form['type'][$key]); + $row[] = form_render($form['datetime'][$key]); + $row[] = form_render($form['preview'][$key]); + $rows[] = $row; + } + + } + else { + $rows[] = array(array('data' => t('There are no items in your trash'), 'colspan' => '5')); + } + + $output .= theme('table', $header, $rows); + if ($form['pager']['#value']) { + $output .= form_render($form['pager']); + } + + return $output; +} + +function system_trash($did, $rid, $root_row, $preview, $query) { + global $user; + static $same_did; + static $summed_preview; + // Grab the args for the query + $all_args = func_get_args(); + if (count($all_args) > 5) { + $args = array_slice($all_args, 5); + } + else { + $args = array(); + } + // Turn delete into select and get result + $delete = preg_replace('/DELETE.*?FROM/i', 'SELECT * FROM', $query); + $result = db_query($delete, $args); + $rid = $rid ? $rid : ''; + $root_row = $root_row ? $root_row : ''; + // Allow modules to add to data storage array + $recover = module_invoke_all('system_trash', $did, $rid, $root_row); + // Grab table name and store + $table = preg_match('/^DELETE FROM \{([^}]+)/', $query, $m); + $recover['table'] = $m[1]; + // Loop through result inserting rows into deleted table + while ($recover['data'] = db_fetch_array($result)) { + $data = serialize($recover); + $tid = db_next_id('{deleted}_tid'); + db_query("INSERT INTO {deleted} SET tid = %d, did = %d, rid = %d, uid = %d, root_row = '%s', data = '%s', timestamp = %d", $tid, $did, $rid, $user->uid, $root_row, $data, time()); + $root_row = ''; + } + // Only one message for each did + if ($same_did != $did) { + drupal_set_message(t('Click %here to recover from %trash.', array('%here' => l(t('here'), "trash/recover/$did"), '%trash' => l(t('trash'), "admin/trash")))); + $same_did = $did; + $summed_preview = array(); + } + // Insert new preview summary + if ($preview) { + $summed_preview[] = $preview; + } + $insert_preview = $summed_preview ? serialize($summed_preview) : ''; + db_query("UPDATE {deleted} SET preview = '%s' WHERE did = %d AND root_row !=''", $insert_preview, $did); + // Delete from original table + db_query($query, $args); +} + +function system_trash_preview($tid) { + global $user; + $op = $_POST['op']; + $view_all = user_access('administer trash'); + $insert = db_fetch_object(db_query('SELECT * FROM {deleted} WHERE tid = %d', $tid)); + if ($view_all || $insert->uid == $user->uid) { + $did = $insert->did; + $output = ''; + $form['buttons'] = array('#prefix' => '
    ', '#suffix' => '
    '); + $form['buttons']['recover'] = array('#type' => 'submit', '#value' => t('Recover')); + if ($view_all) { + $form['buttons']['delete'] = array('#type' => 'button', '#button_type' => 'submit', '#value' => t('Delete selected')); + } + + $form['did']['#tree'] = TRUE; + $form['did'][$did] = array('#type' => 'hidden', '#value' => 1); + + if ($op != t('Delete')) { + $output .= drupal_get_form('system_trash_preview', $form, 'system_trash_recover_delete'); + } + + // If you are attempting to delete nodes, display the multiple deletion form. + if ($op && ($op != t('Recover'))) { + $output = system_trash_multiple_delete_confirm(); + } + // Grab preview data + $values = array(); + $data = unserialize($insert->preview); + $header = array(); + $rows = array(); + if ($data) { + foreach ($data as $preview_section) { + foreach ($preview_section as $key => $value) { + $rows[] = array(''. t($key) . ': ', t($value)); + } + $rows[] = array(array('data' => '', 'colspan' => 2)); + } + } + $output .= '
    '; + $output .= '
    '. t('Trashed') .': '. format_date($insert->timestamp, 'small'); + $output .= '
    '. t('Type') .': '. t($insert->root_row); + $output .= '
    '; + $output .= '

    '. t('Data') .':

    '; + $output .= theme('table', $header, $rows);; + return $output; + } + else { + drupal_access_denied(); + } +} + +function system_trash_recover_delete_execute($form_id, $form_values) { + $op = $_POST['op']; + $form_values['did'] = $form_values['did'] ? $form_values['did'] : array(); + $dids = array_keys($form_values['did'], 1); + if ((count($dids) == 0) && $op != t('Delete all') && $form_values['operation'] != t('Delete all')) { + drupal_set_message(t('No items were selected. Select items in order to perform the operation')); + drupal_goto('admin/trash'); + } + if ($op == t('Recover')) { + foreach ($dids as $did) { + $result = db_query('SELECT * FROM {deleted} WHERE did = %d', $did); + // Loop through reinserting all rows + while ($insert = db_fetch_object($result)) { + $values = array(); + $recover = unserialize($insert->data); + foreach ($recover['data'] as $key => $value) { + $values[] = is_numeric($value) ? '%d' : '\'%s\''; + // Best guess at recovered node if present + if ($key == 'nid') { + $nid = $value; + } + } + db_query('INSERT INTO {'. $recover['table']. '} VALUES('. implode(', ', $values). ')', $recover['data']); + } + module_invoke_all('system_trash_recover', $did); + db_query('DELETE FROM {deleted} WHERE did = %d', $did); + } + if ($form_id == 'system_trash_preview') { + $message = $nid ? t('The %item has been recovered', array('%item' => l(t('item'), "node/$nid"))) : t('The item has been recovered'); + } + else { + $message = t('The items have been recovered'); + } + } + if ($form_values['confirm']) { + switch ($form_values['operation']) { + case t('Delete selected'): + foreach ($dids as $did) { + module_invoke_all('system_trash_delete', $did); + db_query('DELETE FROM {deleted} WHERE did = %d', $did); + } + $message = t('Permanent delete successful'); + break; + case t('Delete all'): + module_invoke_all('system_trash_delete', 'all'); + db_query('DELETE FROM {deleted}'); + $message = t('Permanent delete successful'); + break; + } + } + drupal_set_message($message); + drupal_goto('admin/trash'); +} + +function system_trash_multiple_delete_confirm() { + $op = $_POST['op']; + $edit = $_POST['edit']; + $edit['did'] = $edit['did'] ? $edit['did'] : array(); + $dids = array_keys($edit['did'], 1); + if ($edit['confirm']) { + $form['operation'] = array('#type' => 'hidden', '#value' => $edit['operation']); + $_POST['edit']['form_id'] = 'system_trash_recover_delete'; + } + else { + $form['operation'] = array('#type' => 'hidden', '#value' => $op); + } + $form['did']['#tree'] = TRUE; + foreach ($dids as $did) { + $form['did'][$did] = array('#type' => 'hidden', '#value' => 1); + } + $item = (count($dids) == 1) ? 'this item' : 'these items'; + $output = confirm_form('system_trash_recover_delete', $form, + t('Are you sure you want to permanently delete %item?', array('%item' => $item)), + 'admin/trash', t('This operation cannot be undone.'), + t('Delete'), t('Cancel')); + return $output; +} + +function next_delete_id() { + return db_next_id('{deleted}_did'); +} Index: modules/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy.module,v retrieving revision 1.232 diff -u -F^function -r1.232 taxonomy.module --- modules/taxonomy.module 21 Oct 2005 11:12:46 -0000 1.232 +++ modules/taxonomy.module 29 Oct 2005 12:51:52 -0000 @@ -179,14 +179,16 @@ function taxonomy_save_vocabulary(&$edit function taxonomy_del_vocabulary($vid) { $vocabulary = 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(); + $preview = array('name' => $vocabulary->name, 'description' => $vocabulary->description); + system_trash($did, $vid, 'vocabulary', $preview, 'DELETE FROM {vocabulary} WHERE vid = %d', $vid); + system_trash($did, $vid, NULL, NULL, 'DELETE FROM {vocabulary_node_types} 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(); @@ -194,20 +196,6 @@ function taxonomy_del_vocabulary($vid) { return SAVED_DELETED; } -function _taxonomy_confirm_del_vocabulary($vid) { - $vocabulary = taxonomy_get_vocabulary($vid); - - $form['type'] = array('#type' => 'hidden', '#value' => 'vocabulary'); - $form['vid'] = array('#type' => 'hidden', '#value' => $vid); - $form['name'] = array('#type' => 'hidden', '#value' => $vocabulary->name); - return confirm_form('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_form_term($edit = array()) { $vocabulary_id = isset($edit['vid']) ? $edit['vid'] : arg(4); $vocabulary = taxonomy_get_vocabulary($vocabulary_id); @@ -324,7 +312,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(); @@ -341,37 +336,37 @@ function taxonomy_del_term($tid) { } $term = 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); - + if (!$recurse && !$vocab_did) { + $root_row = 'taxonomy term'; + $preview = array('root term' => $term->name, 'description' => $term->description); + $recurse = 1; + } + else { + $count++; + $root_row = NULL; + $preview = array(); + if ($term->name) { + $preview['term'. $count] = $term->name; + } + if ($term->description) { + $preview['description'. $count] = $term->description; + } + } + drupal_set_message(t('Moved term %name to trash.', array('%name' => theme('placeholder', $term->name)))); + system_trash($did, $term->vid, $root_row, $preview, 'DELETE FROM {term_data} WHERE tid = %d', $tid); + system_trash($did, $term->vid, NULL, NULL, 'DELETE FROM {term_hierarchy} WHERE tid = %d', $tid); + system_trash($did, $term->vid, NULL, NULL, 'DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid); + system_trash($did, $term->vid, NULL, NULL, 'DELETE FROM {term_synonym} WHERE tid = %d', $tid); + system_trash($did, $term->vid, NULL, NULL, 'DELETE FROM {term_node} WHERE tid = %d', $tid); + $term->did = $did; module_invoke_all('taxonomy', 'delete', 'term', $term); - drupal_set_message(t('Deleted term %name.', array('%name' => theme('placeholder', $term->name)))); } $tids = $orphans; } - cache_clear_all(); } -function _taxonomy_confirm_del_term($tid) { - $term = taxonomy_get_term($tid); - - $form['type'] = array('#type' => 'hidden', '#value' => 'term'); - $form['tid'] = array('#type' => 'hidden', '#value' => $tid); - return confirm_form('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')); -} - /** * Generate a tabular listing of administrative functions for vocabularies. */ @@ -593,7 +588,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. @@ -654,13 +649,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') { @@ -1041,7 +1029,7 @@ function taxonomy_nodeapi($node, $op, $a taxonomy_node_save($node->nid, $node->taxonomy); break; case 'delete': - taxonomy_node_delete($node->nid); + system_trash($node->did, $node->nid, NULL, NULL, 'DELETE FROM {term_node} WHERE nid = %d', $node->nid); break; case 'validate': taxonomy_node_validate($node); @@ -1170,20 +1158,9 @@ function taxonomy_admin() { } break; case t('Delete'): - if (!$edit['confirm']) { - if (arg(3) == 'vocabulary') { - $output = _taxonomy_confirm_del_vocabulary($edit['vid']); - } - else { - $output = _taxonomy_confirm_del_term($edit['tid']); - } - break; - } - else { - $deleted_name = $edit['name']; - $edit['name'] = 0; - // fall through: - } + $deleted_name = $edit['name']; + $edit['name'] = 0; + // fall through: case t('Submit'): if (arg(3) == 'vocabulary') { switch (taxonomy_save_vocabulary($edit)) { @@ -1194,7 +1171,7 @@ function taxonomy_admin() { drupal_set_message(t('Updated vocabulary %name.', array('%name' => theme('placeholder', $edit['name'])))); break; case SAVED_DELETED: - drupal_set_message(t('Deleted vocabulary %name.', array('%name' => theme('placeholder', $deleted_name)))); + drupal_set_message(t('Moved vocabulary %name to trash.', array('%name' => theme('placeholder', $deleted_name)))); break; } } Index: modules/upload.module =================================================================== RCS file: /cvs/drupal/drupal/modules/upload.module,v retrieving revision 1.56 diff -u -F^function -r1.56 upload.module --- modules/upload.module 28 Oct 2005 14:04:20 -0000 1.56 +++ modules/upload.module 29 Oct 2005 12:51:53 -0000 @@ -289,7 +289,12 @@ function upload_nodeapi(&$node, $op, $ar break; case 'delete': - upload_delete($node); + $file_info = upload_load($node); + foreach ($file_info as $file) { + $i++; + $preview['file'. $i] = $file->filename; + } + system_trash($node->did, $node->nid, NULL, $preview, "DELETE FROM {files} WHERE nid = %d", $node->nid); break; case 'search result': return $node->files ? format_plural(count($node->files), '1 attachment', '%count attachments') : null; @@ -380,14 +385,6 @@ function upload_save($node) { return; } -function upload_delete($node) { - $node->files = upload_load($node); - foreach ($node->files as $file) { - file_delete($file->filepath); - } - db_query("DELETE FROM {files} WHERE nid = %d", $node->nid); -} - function upload_form($node) { drupal_add_js('misc/progress.js'); drupal_add_js('misc/upload.js'); @@ -512,3 +509,35 @@ function upload_js() { print drupal_call_js('window.parent.iframeHandler', $output); exit; } + +function upload_system_trash($did, $rid, $root_row) { + // Store file info in case of later permanent deletion + if ($root_row == 'node') { + $node = node_load($rid); + $file_info = upload_load($node); + $files = array(); + foreach ($file_info as $file) { + $filepaths[] = $file->filepath; + } + $recover['files'] = $filepaths; + return $recover; + } +} + +function upload_system_trash_delete($did) { + // Grab all node deletions and delete associated files + if ($did == 'all') { + $result = db_query("SELECT * FROM {deleted} WHERE root_row = 'node'"); + } + else { + $result = db_query("SELECT * FROM {deleted} WHERE did = %d AND root_row = 'node'", $did); + } + while ($node = db_fetch_object($result)) { + $data = unserialize($node->data); + if ($data['files']) { + foreach ($data['files'] as $filepath) { + file_delete($filepath); + } + } + } +} Index: modules/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user.module,v retrieving revision 1.524 diff -u -F^function -r1.524 user.module --- modules/user.module 28 Oct 2005 00:37:06 -0000 1.524 +++ modules/user.module 29 Oct 2005 12:51:55 -0000 @@ -1257,18 +1257,16 @@ function user_edit($category = 'account' } } else 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); - 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', $form, t('Are you sure you want to delete the account %name?', array('%name' => theme('placeholder', $account->name))), 'user/'. $account->uid, t('Deleting a user will remove all their submissions as well. This action cannot be undone.'), t('Delete')); - } + $did = next_delete_id(); + $preview = array('name' => $account->name, 'email' => $account->mail); + system_trash($did, $account->uid, 'user', $preview, 'DELETE FROM {users} WHERE uid = %d', $account->uid); + db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid); + drupal_set_message(t('The account has been moved to trash.')); + system_trash($did, $account->uid, NULL, NULL, 'DELETE FROM {users_roles} WHERE uid = %d', $account->uid); + system_trash($did, $account->uid, NULL, NULL, 'DELETE FROM {authmap} WHERE uid = %d', $account->uid); + $account->did = $did; + module_invoke_all('user', 'delete', $edit, $account); + drupal_goto('admin/user'); } else if ($_POST['op'] == t('Delete')) { // Note: we redirect from user/uid/edit to user/uid/delete to make the tabs disappear. @@ -1458,21 +1456,10 @@ 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)); - - $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; -} - -function user_admin_access_delete_confirm_execute($form_id, $edit) { - db_query('DELETE FROM {access} WHERE aid = %d', $edit['aid']); + $did = next_delete_id(); + $preview = array('type' => $access_types[$edit->type], 'rule' => $edit->mask); drupal_set_message(t('The access rule has been deleted.')); + system_trash($did, $aid, 'access rule', $preview, 'DELETE FROM {access} WHERE aid = %d', $aid); drupal_goto('admin/access/rules'); } @@ -1665,8 +1652,11 @@ 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(); + $preview = db_fetch_array(db_query('SELECT name FROM {role} WHERE rid = %d', $id)); + drupal_set_message(t('The role has been moved to trash. All users with only this role have been moved to the authenticated users pool.')); + system_trash($did, $id, 'role', $preview, 'DELETE FROM {role} WHERE rid = %d', $id); + system_trash($did, $id, NULL, NULL, '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); @@ -1677,13 +1667,12 @@ function user_admin_role() { } if ($uid) { - db_query('DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid)); + system_trash($did, $id, NULL, NULL, 'DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid)); } // Users with only the deleted role are put back in the authenticated users pool. db_query('UPDATE {users_roles} SET rid = %d WHERE rid = %d', _user_authenticated_id(), $id); - drupal_set_message(t('The role has been deleted.')); drupal_goto('admin/access/roles'); } else if ($op == t('Add role')) {