? files ? undo.patch ? modules/devel ? modules/signup ? sites/localhost Index: database/database.mysql =================================================================== RCS file: /cvs/drupal/drupal/database/database.mysql,v retrieving revision 1.204 diff -u -F^function -r1.204 database.mysql --- database/database.mysql 14 Nov 2005 22:23:11 -0000 1.204 +++ database/database.mysql 15 Nov 2005 16:43:04 -0000 @@ -243,6 +243,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.146 diff -u -F^function -r1.146 database.pgsql --- database/database.pgsql 14 Nov 2005 22:23:11 -0000 1.146 +++ database/database.pgsql 15 Nov 2005 16:43:05 -0000 @@ -238,6 +238,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.147 diff -u -F^function -r1.147 updates.inc --- database/updates.inc 14 Nov 2005 22:23:11 -0000 1.147 +++ database/updates.inc 15 Nov 2005 16:43:06 -0000 @@ -104,7 +104,8 @@ "2005-10-23" => "update_151", "2005-10-28" => "update_152", "2005-11-03" => "update_153", - "2005-11-14" => "update_154" + "2005-11-14" => "update_154", + "2005-11-15" => "update_155" ); function update_110() { @@ -1159,6 +1160,43 @@ function update_154() { return $ret; } +function update_155() { + $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; +} + /** Index: includes/locale.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/locale.inc,v retrieving revision 1.59 diff -u -F^function -r1.59 locale.inc --- includes/locale.inc 13 Nov 2005 02:32:18 -0000 1.59 +++ includes/locale.inc 15 Nov 2005 16:43:08 -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.188 diff -u -F^function -r1.188 block.module --- modules/block.module 13 Nov 2005 08:26:01 -0000 1.188 +++ modules/block.module 15 Nov 2005 16:43:09 -0000 @@ -421,26 +421,17 @@ function block_box_add_execute($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); - - 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')); -} - -/** - * Deletion of custom blocks. - */ -function block_box_delete_confirm_execute($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'])))); + $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'); -}; - +} function block_box_form($edit = array()) { $form['title'] = array('#type' => 'textfield', '#title' => t('Block title'), '#default_value' => $edit['title'], '#maxlength' => 64, '#description' => t('The title of the block as shown to the user.')); Index: modules/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book.module,v retrieving revision 1.329 diff -u -F^function -r1.329 book.module --- modules/book.module 12 Nov 2005 11:26:16 -0000 1.329 +++ modules/book.module 15 Nov 2005 16:43:10 -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); } /** @@ -303,8 +303,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.390 diff -u -F^function -r1.390 comment.module --- modules/comment.module 15 Nov 2005 07:29:47 -0000 1.390 +++ modules/comment.module 15 Nov 2005 16:43:11 -0000 @@ -268,8 +268,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': @@ -981,12 +987,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); @@ -998,20 +1002,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; } /** @@ -1361,9 +1355,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.31 diff -u -F^function -r1.31 contact.module --- modules/contact.module 14 Nov 2005 22:23:11 -0000 1.31 +++ modules/contact.module 15 Nov 2005 16:43:12 -0000 @@ -213,19 +213,12 @@ function contact_admin_edit($cid = NULL) } function contact_admin_delete($cid) { - $info = db_fetch_object(db_query("SELECT cid, category FROM {contact} WHERE cid = %d", $cid)); - 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', $info->category))), - 'admin/contact', - t('This action cannot be undone.'), - t('Delete'), - t('Cancel')); - } - else { - db_query("DELETE FROM {contact} WHERE cid = %d", $cid); + $did = next_delete_id(); + $info = db_fetch_object(db_query("SELECT cid, category FROM {contact} WHERE cid = %d", $cid)); + $preview = array('category' => $info->category); + drupal_set_message(t('Contact category %category moved to trash', array('%category' => $info->category))); + system_trash($did, $category, 'contact', $preview, "DELETE FROM {contact} WHERE cid = %d", $cid); drupal_goto('admin/contact'); - } } Index: modules/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter.module,v retrieving revision 1.83 diff -u -F^function -r1.83 filter.module --- modules/filter.module 12 Nov 2005 11:26:16 -0000 1.83 +++ modules/filter.module 15 Nov 2005 16:43:13 -0000 @@ -350,33 +350,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.284 diff -u -F^function -r1.284 forum.module --- modules/forum.module 12 Nov 2005 11:26:16 -0000 1.284 +++ modules/forum.module 15 Nov 2005 16:43:14 -0000 @@ -154,7 +154,9 @@ function forum_taxonomy($op, $type, $obj 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)) { - node_delete($node->nid); + $node = node_load($node->nid); + $node->did = $object->did; + node_delete($node); } } elseif ($op == 'delete' && $type == 'vocabulary' && $object->vid == _forum_get_vid()) { Index: modules/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale.module,v retrieving revision 1.129 diff -u -F^function -r1.129 locale.module --- modules/locale.module 1 Nov 2005 10:17:34 -0000 1.129 +++ modules/locale.module 15 Nov 2005 16:43:15 -0000 @@ -301,39 +301,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.548 diff -u -F^function -r1.548 node.module --- modules/node.module 13 Nov 2005 02:43:33 -0000 1.548 +++ modules/node.module 15 Nov 2005 16:43:17 -0000 @@ -859,6 +859,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'), @@ -1093,16 +1096,18 @@ 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'); } } @@ -1153,7 +1158,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; @@ -1190,28 +1195,81 @@ 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' => '', '#tree' => TRUE); - 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, '#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($nid); +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.')); } @@ -1327,11 +1385,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"); @@ -1849,16 +1910,36 @@ 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' => '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') ); + $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; } /** @@ -1866,7 +1947,10 @@ function node_delete_confirm() { */ function node_delete_confirm_execute($form_id, $form_values) { if ($form_values['confirm']) { - node_delete($form_values['nid']); + // 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'); } } @@ -1874,28 +1958,40 @@ function node_delete_confirm_execute($fo /** * Delete a node. */ -function node_delete($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); - $node = node_load($nid); + // Call the node-specific callback (if any): + node_invoke($node, 'delete'); + node_invoke_nodeapi($node, 'delete'); - 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); + // Clear the cache so an anonymous poster can see the node being deleted. + cache_clear_all(); - // 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)))); + // 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)))); } /** Index: modules/path.module =================================================================== RCS file: /cvs/drupal/drupal/modules/path.module,v retrieving revision 1.71 diff -u -F^function -r1.71 path.module --- modules/path.module 12 Nov 2005 11:26:16 -0000 1.71 +++ modules/path.module 15 Nov 2005 16:43:18 -0000 @@ -99,17 +99,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) { @@ -216,7 +224,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.179 diff -u -F^function -r1.179 poll.module --- modules/poll.module 12 Nov 2005 11:26:16 -0000 1.179 +++ modules/poll.module 15 Nov 2005 16:43:18 -0000 @@ -84,9 +84,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.118 diff -u -F^function -r1.118 profile.module --- modules/profile.module 13 Nov 2005 08:33:44 -0000 1.118 +++ modules/profile.module 15 Nov 2005 16:43:19 -0000 @@ -554,18 +554,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.212 diff -u -F^function -r1.212 statistics.module --- modules/statistics.module 13 Nov 2005 02:40:18 -0000 1.212 +++ modules/statistics.module 15 Nov 2005 16:43:20 -0000 @@ -477,7 +477,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.255 diff -u -F^function -r1.255 system.module --- modules/system.module 14 Nov 2005 22:19:14 -0000 1.255 +++ modules/system.module 15 Nov 2005 16:43:22 -0000 @@ -47,7 +47,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'); } /** @@ -139,6 +139,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; @@ -1143,6 +1155,259 @@ function theme_search_box($form) { return $output; } +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'); +} + /** * Output a confirmation form * Index: modules/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy.module,v retrieving revision 1.236 diff -u -F^function -r1.236 taxonomy.module --- modules/taxonomy.module 12 Nov 2005 11:26:16 -0000 1.236 +++ modules/taxonomy.module 15 Nov 2005 16:43:23 -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); @@ -327,7 +315,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(); @@ -344,37 +339,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. */ @@ -596,7 +591,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. @@ -657,13 +652,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') { @@ -1048,7 +1036,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); @@ -1177,20 +1165,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)) { @@ -1201,7 +1178,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.59 diff -u -F^function -r1.59 upload.module --- modules/upload.module 13 Nov 2005 02:00:37 -0000 1.59 +++ modules/upload.module 15 Nov 2005 16:43:24 -0000 @@ -301,7 +301,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; @@ -392,14 +397,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'); @@ -524,3 +521,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.530 diff -u -F^function -r1.530 user.module --- modules/user.module 14 Nov 2005 21:49:47 -0000 1.530 +++ modules/user.module 15 Nov 2005 16:43:27 -0000 @@ -1260,18 +1260,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. @@ -1461,21 +1459,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'); } @@ -1668,8 +1655,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); @@ -1680,13 +1670,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')) {