=== modified file 'database/database.mysql'
--- database/database.mysql
+++ database/database.mysql
@@ -272,6 +272,44 @@ CREATE TABLE client_system (
/*!40100 DEFAULT CHARACTER SET utf8 */ ;
--
+-- Table structure for table `deleted`
+--
+
+CREATE TABLE deleted (
+ did int(10) unsigned NOT NULL default '0',
+ uid int(10) unsigned NOT NULL default '0',
+ rid varchar(255) NOT NULL default '',
+ name varchar(255) NOT NULL default '',
+ description varchar(255) NOT NULL default '',
+ title varchar(255) NOT NULL default '',
+ preview longtext NOT NULL,
+ extra longtext NOT NULL,
+ timestamp int(10) unsigned NOT NULL default '0',
+ PRIMARY KEY (did),
+ KEY uid (uid)
+) TYPE=MyISAM
+/*!40100 DEFAULT CHARACTER SET utf8 */ ;
+
+--
+-- Table structure for table `deleted_data`
+--
+
+CREATE TABLE deleted_data (
+ tid int(10) unsigned NOT NULL default '0',
+ did int(10) unsigned NOT NULL default '0',
+ dtable varchar(255) NOT NULL default '',
+ row_id int(10) unsigned NOT NULL default '0',
+ dcolumn varchar(255) NOT NULL default '',
+ data longtext NOT NULL,
+ PRIMARY KEY (tid),
+ KEY did (did),
+ KEY dtable (dtable),
+ KEY row_id (row_id),
+ KEY dcolumn (dcolumn)
+) TYPE=MyISAM
+/*!40100 DEFAULT CHARACTER SET utf8 */ ;
+
+--
-- Table structure for table 'files'
--
=== modified file 'database/database.pgsql'
--- database/database.pgsql
+++ database/database.pgsql
@@ -259,6 +259,42 @@ CREATE TABLE client_system (
);
--
+-- Table structure for table `deleted`
+--
+
+CREATE TABLE deleted (
+ did SERIAL,
+ uid integer NOT NULL default '0',
+ rid text NOT NULL default '',
+ name text NOT NULL default '',
+ description text NOT NULL default '',
+ title text NOT NULL default '',
+ preview text NOT NULL default '',
+ extra text NOT NULL default '',
+ timestamp integer NOT NULL default '0',
+ PRIMARY KEY (did)
+);
+CREATE INDEX deleted_uid_idx ON deleted(uid);
+
+--
+-- Table structure for table `deleted_data`
+--
+
+CREATE TABLE deleted_data (
+ tid SERIAL,
+ did integer NOT NULL default '0',
+ dtable text NOT NULL default '',
+ row_id integer NOT NULL default '0',
+ dcolumn text NOT NULL default '',
+ data text NOT NULL default '',
+ PRIMARY KEY (tid)
+);
+CREATE INDEX deleted_data_did_idx ON deleted_data(did);
+CREATE INDEX deleted_data_dtable_idx ON deleted_data(dtable);
+CREATE INDEX deleted_data_row_id_idx ON deleted_data(row_id);
+CREATE INDEX deleted_data_dcolumn_idx ON deleted_data(dcolumn);
+
+--
-- Table structure for table 'files'
--
=== modified file 'database/updates.inc'
--- database/updates.inc
+++ database/updates.inc
@@ -1344,7 +1344,6 @@ function system_update_164() {
}
}
}
-
$ret[] = update_sql('ALTER TABLE {poll} DROP polled');
return $ret;
@@ -1585,3 +1584,73 @@ function system_update_171() {
$ret[] = update_sql('DELETE FROM {users_roles} WHERE rid IN ('. DRUPAL_ANONYMOUS_RID. ', '. DRUPAL_AUTHENTICATED_RID. ')');
return $ret;
}
+
+/**
+ * Creates tables for the undo / trash can function.
+ */
+function system_update_172() {
+ $ret = array();
+
+ switch ($GLOBALS['db_type']) {
+ case 'mysqli':
+ case 'mysql':
+ $ret[] = update_sql("CREATE TABLE deleted (
+ did int(10) unsigned NOT NULL default '0',
+ uid int(10) unsigned NOT NULL default '0',
+ rid varchar(255) NOT NULL default '',
+ name varchar(255) NOT NULL default '',
+ description varchar(255) NOT NULL default '',
+ title varchar(255) NOT NULL default '',
+ preview longtext NOT NULL,
+ extra longtext NOT NULL,
+ timestamp int(10) unsigned NOT NULL default '0',
+ PRIMARY KEY (did),
+ KEY uid (uid)
+ ) TYPE=MyISAM;");
+ $ret[] = update_sql("CREATE TABLE deleted_data (
+ tid int(10) unsigned NOT NULL default '0',
+ did int(10) unsigned NOT NULL default '0',
+ dtable varchar(255) NOT NULL default '',
+ row_id int(10) unsigned NOT NULL default '0',
+ dcolumn varchar(255) NOT NULL default '',
+ data longtext NOT NULL,
+ PRIMARY KEY (tid),
+ KEY did (did),
+ KEY dtable (dtable),
+ KEY row_id (row_id),
+ KEY dcolumn (dcolumn)
+ ) TYPE=MyISAM");
+ break;
+ case 'pgsql':
+ $ret[] = update_sql("CREATE TABLE deleted (
+ did SERIAL,
+ uid integer NOT NULL default '0',
+ rid text NOT NULL default '',
+ name text NOT NULL default '',
+ description text NOT NULL default '',
+ title text NOT NULL default '',
+ preview text NOT NULL default '',
+ extra text NOT NULL default '',
+ timestamp integer NOT NULL default '0',
+ PRIMARY KEY (did)
+ )");
+ $ret[] = update_sql("CREATE INDEX deleted_uid_idx ON deleted(uid)");
+ $ret[] = update_sql("CREATE TABLE deleted_data (
+ tid SERIAL,
+ did integer NOT NULL default '0',
+ dtable text NOT NULL default '',
+ row_id integer NOT NULL default '0',
+ dcolumn text NOT NULL default '',
+ data text NOT NULL default '',
+ PRIMARY KEY (tid)
+ )");
+ $ret[] = update_sql("CREATE INDEX deleted_data_did_idx ON deleted_data(did)");
+ $ret[] = update_sql("CREATE INDEX deleted_data_dtable_idx ON deleted_data(dtable)");
+ $ret[] = update_sql("CREATE INDEX deleted_data_row_id_idx ON deleted_data(row_id)");
+ $ret[] = update_sql("CREATE INDEX deleted_data_dcolumn_idx ON deleted_data(dcolumn)");
+ break;
+ default:
+ break;
+ }
+ return $ret;
+}
=== modified file 'misc/grippie.png' (properties changed)
=== modified file 'modules/block.module'
--- modules/block.module
+++ modules/block.module
@@ -436,25 +436,24 @@ function block_box_add_submit($form_id,
}
/**
- * Menu callback; confirm deletion of custom blocks.
+ * Deletion of custom blocks.
*/
function block_box_delete($bid = 0) {
+
$box = block_box_get($bid);
- $form['info'] = array('#type' => 'hidden', '#value' => $box['info'] ? $box['info'] : $box['title']);
- $form['bid'] = array('#type' => 'hidden', '#value' => $bid);
+ $did = next_delete_id();
- return confirm_form('block_box_delete_confirm', $form, t('Are you sure you want to delete the block %name?', array('%name' => theme('placeholder', $info))), 'admin/block', '', t('Delete'), t('Cancel'));
-}
+ //build trashbin preview
+ $preview = array('info' => $box['info'], 'body' => $box['body']);
+
+ drupal_set_message(t('The block %name has been moved to the trash.', array('%name' => theme('placeholder', $box['info']))));
+
+ $trash_data = array('did' => $did, 'rid' => $bid, 'name' => 'block', 'title' => $box['title'], 'preview' => $preview);
+ system_trash($trash_data, 'DELETE FROM {boxes} WHERE bid = %d', $bid);
-/**
- * Deletion of custom blocks.
- */
-function block_box_delete_confirm_submit($form_id, $form_values) {
- db_query('DELETE FROM {boxes} WHERE bid = %d', $form_values['bid']);
- drupal_set_message(t('The block %name has been removed.', array('%name' => theme('placeholder', $form_values['info']))));
cache_clear_all();
drupal_goto('admin/block');
-};
+}
function block_box_form($edit = array()) {
$form['info'] = array(
=== modified file 'modules/book.module'
--- modules/book.module
+++ modules/book.module
@@ -221,7 +221,8 @@ function book_update($node) {
* Implementation of hook_delete().
*/
function book_delete(&$node) {
- db_query('DELETE FROM {book} WHERE nid = %d', $node->nid);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {book} WHERE nid = %d', $node->nid);
}
/**
@@ -302,8 +303,16 @@ 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();
+
+ // Build trash preview
+ $preview = array('body' => $node->body);
+
+ drupal_set_message(t('The post has been removed from the book and placed in trash.'));
+
+ $trash_data = array('did' => $did, 'rid' => $node->nid, 'name' => 'node', 'description' => 'book', 'title' => $node->title, 'preview' => $preview);
+ system_trash($trash_data, 'DELETE FROM {book} WHERE nid = %d', $node->nid);
+
drupal_goto("node/$node->nid");
break;
@@ -469,7 +478,8 @@ function book_nodeapi(&$node, $op, $teas
}
break;
case 'delete revision':
- db_query('DELETE FROM {book} WHERE vid = %d', $node->vid);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {book} WHERE vid = %d', $node->vid);
break;
}
}
=== modified file 'modules/comment.module'
--- modules/comment.module
+++ modules/comment.module
@@ -292,8 +292,19 @@ 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);
+
+ // Build trash preview
+ while ($comment = db_fetch_object($result)) {
+ $i++;
+ $preview['subject'. $i] = $comment->subject;
+ $preview['comment'. $i] = $comment->comment;
+ }
+
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid);
+ $trash_data['preview'] = $preview;
+ system_trash($trash_data, 'DELETE FROM {comments} WHERE nid = %d', $node->nid);
break;
case 'update index':
@@ -888,12 +899,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 the trash.'));
// Delete comment and its replies.
_comment_delete_thread($comment);
@@ -905,20 +914,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;
}
/**
@@ -1624,9 +1623,22 @@ function theme_comment_post_forbidden($n
}
function _comment_delete_thread($comment) {
+ static $did;
+
+ // Continue in same deletion package if $did already exists, else start a new package
+ if ($did) {
+ $trash_data = array('did' => $did);
+ }
+ else {
+ $did = next_delete_id();
+
+ // Build trash preview
+ $preview = array('comment' => $comment->comment);
+ $trash_data = array('did' => $did, 'rid' => $comment->nid, 'name' => 'comment', 'title' => $comment->subject, 'preview' => $preview);
+ }
// Delete the comment:
- db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
- watchdog('content', t('Comment: deleted %subject.', array('%subject' => theme('placeholder', $comment->subject))));
+ system_trash($trash_data, 'DELETE FROM {comments} WHERE cid = %d', $comment->cid);
+ watchdog('content', t('Comment: moved %subject to the trash.', array('%subject' => theme('placeholder', $comment->subject))));
comment_invoke_comment($comment, 'delete');
=== modified file 'modules/contact.module'
--- modules/contact.module
+++ modules/contact.module
@@ -289,21 +289,12 @@ function contact_admin_edit_submit($form
}
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));
+ drupal_set_message(t('Contact category %category moved to the trash', array('%category' => theme('placeholder', $info->category))));
+ $trash_data = array('did' => $did, 'rid' => $cid, 'name' => 'contact', 'description' => 'contact form', 'title' => $info->category);
+ system_trash($trash_data, "DELETE FROM {contact} WHERE cid = %d", $cid);
drupal_goto('admin/contact');
- }
}
function contact_admin() {
=== modified file 'modules/filter.module'
--- modules/filter.module
+++ modules/filter.module
@@ -342,33 +342,26 @@ 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));
+ 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();
- $form['format'] = array('#type' => 'hidden', '#value' => $format->format);
- $form['name'] = array('#type' => 'hidden', '#value' => $format->name);
+ drupal_set_message(t('Input format %format moved to the trash.', array('%format' => theme('placeholder', $result->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'));
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, "DELETE FROM {filter_formats} WHERE format = %d", $format);
+ $trash_data = array_merge($trash_data, array('rid' => $format, 'name' => 'filter', 'description' => 'input filter', 'title' => $result->name));
+ system_trash($trash_data, "DELETE FROM {filters} 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');
}
/**
=== modified file 'modules/forum.module'
--- modules/forum.module
+++ modules/forum.module
@@ -77,7 +77,8 @@ function forum_perm() {
function forum_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'delete revision':
- db_query('DELETE FROM {forum} WHERE vid = %d', $node->vid);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {forum} WHERE vid = %d', $node->vid);
break;
}
}
@@ -166,7 +167,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()) {
=== modified file 'modules/locale.module'
--- modules/locale.module
+++ modules/locale.module
@@ -301,23 +301,6 @@ 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') {
@@ -325,15 +308,28 @@ function locale_admin_manage_delete_scre
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();
+
+ // Build trash preview
+ $preview = array('name' => $languages['name'][$langcode]);
+
+ $message = t('The language %locale has been moved to the trash.', array('%locale' => theme('placeholder', t($languages['name'][$langcode]))));
+ drupal_set_message($message);
+ watchdog('locale', $message);
+
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, "DELETE FROM {locales_target} WHERE locale = '%s'", $langcode);
+ $trash_data = array_merge($trash_data, array('rid' => $langcode, 'name' => 'locale', 'description' => 'translation', 'title' => $langcode, 'preview' => $preview));
+ system_trash($trash_data, "DELETE FROM {locales_meta} 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');
}
/**
=== modified file 'modules/node.module'
--- modules/node.module
+++ modules/node.module
@@ -864,6 +864,9 @@ function node_menu($may_cache) {
$items[] = array('path' => 'admin/node', 'title' => t('content'),
'callback' => 'node_admin_nodes',
'access' => user_access('administer nodes'));
+ $items[] = array('path' => 'admin/node/delete', 'title' => t('content'),
+ 'type' => MENU_CALLBACK, 'access' => user_access('administer nodes'),
+ 'callback' => 'node_multiple_delete_confirm');
$items[] = array('path' => 'admin/node/overview', 'title' => t('list'),
'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10);
$items[] = array('path' => 'admin/node/action', 'title' => t('content'),
@@ -1201,30 +1204,106 @@ function theme_node_admin_nodes($form) {
return $output;
}
+/**
+ * Deletes nodes from admin batch deletes, or presents a confirmation form.
+ *
+ * @return
+ * A fully themed confimation form for all nodes which have extra data to display, or nothing if no nodes have extra
+ * display data.
+ */
function node_multiple_delete_confirm() {
+
$edit = $_POST['edit'];
+ $options = array();
- $form['nodes'] = array('#prefix' => '
', '#tree' => TRUE);
- // array filter returns only elements with true values
- foreach (array_filter($edit['nodes']) as $nid => $value) {
- $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
- $form['nodes'][$nid] = array('#type' => 'hidden', '#value' => $nid, '#prefix' => '', '#suffix' => check_plain($title) ."\n");
- }
- $form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
-
- return confirm_form('node_multiple_delete_confirm', $form,
- t('Are you sure you want to delete these items?'),
- 'admin/node', t('This action cannot be undone.'),
- t('Delete all'), t('Cancel'));
-}
+ // Keep only checked node's nids
+ $nids = isset($edit['nodes']) ? array_keys($edit['nodes'], !0) : array();
+ foreach ($nids as $nid) {
-function node_multiple_delete_confirm_submit($form_id, $edit) {
- if ($edit['confirm']) {
- foreach ($edit['nodes'] as $nid => $value) {
- node_delete($nid);
+ // Grab any extra display data for the node
+ $node = node_load($nid);
+ $extras = node_invoke_nodeapi($node, 'delete pre');
+
+ // No extras, just delete
+ if (!count($extras)) {
+ node_delete($node);
}
- drupal_set_message(t('The items have been deleted.'));
+ else {
+
+ // Add node and extras to the list only if no module has cancelled deletion for this node.
+ $title = check_plain($node->title);
+ if (in_array(FALSE, $extras)) {
+ drupal_set_message(t('Deletion of %title cancelled', array('%title' => $title)), 'error');
+ }
+ else {
+ $form['title'][$node->nid] = array('#type' => 'markup', '#value' => $title);
+ $form['extras'][$node->nid] = $extras;
+ $options[$node->nid] = '';
+ }
+ }
+ }
+
+ // Back to the admin page if no nodes need confimation, else return a confirm form.
+ if (!count($options)) {
+ drupal_goto('admin/node');
+ }
+ else {
+ $form['title']['#tree'] = TRUE;
+ $form['extras']['#tree'] = TRUE;
+ $form['nodes'] = array('#type' => 'checkboxes', '#options' => $options);
+ $form['#action'] = url('admin/node/delete');
+ return confirm_form('node_multiple_delete_confirm', $form,
+ t('The following items need to be reviewed'),
+ 'admin/node', t('Selected items will be moved to the trash.'),
+ t('Delete selected'), t('Cancel'));
+ }
+}
+
+/**
+ * Themes the confirmation form for admin batch deletes.
+ *
+ * @param $form
+ * The from array to be themed
+ * @return
+ * An HTML string representing the confirmation form
+ */
+function theme_node_multiple_delete_confirm($form) {
+ $rows = array();
+ $header = array('');
+ $output = '';
+
+ // Build one row for each node that needs confirmed
+ foreach(array_keys($form['nodes']['#options']) as $nid) {
+ $rows[] = array(''. ''. form_render($form['title'][$nid]) .''. form_render($form['nodes'][$nid]) .'
'. form_render($form['extras'][$nid]));
+ $rows[] = array('');
+ }
+ $output .= theme('table', $header, $rows);
+ $output .= form_render($form);
+ return $output;
+}
+
+/**
+ * Submits the form data for deletion.
+ *
+ * @param $form_id
+ * The unique form identifier.
+ * @param $form_values
+ * The constructed form array.
+ */
+function node_multiple_delete_confirm_submit($form_id, $form_values) {
+
+ // Only checked values will be deleted
+ foreach ($form_values['nodes'] as $nid => $value) {
+
+ // Merge extra confirm data with node
+ $node = node_load($nid);
+ $node = (array) $node;
+ $node = array_merge($node, $form_values['extras'][$nid]);
+ $node = (object) $node;
+
+ node_delete($node);
}
+ drupal_set_message(t('The items have been deleted.'));
drupal_goto('admin/node');
}
@@ -1343,17 +1422,22 @@ function node_revision_delete($nid, $rev
// Don't delete the current revision
if ($revision != $node->vid) {
$node = node_load($nid, $revision);
+ $did = next_delete_id();
+
+ // Build trash preview
+ $preview = array('body' => $node->body, 'revision' => $revision);
+
+ drupal_set_message(t('Revision %title %revision moved to the trash.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
+
+ $trash_data = array('did' => $did, 'rid' => $nid, 'name' => 'node revision', 'description' => $node->type, 'title' => $node->title, 'preview' => $preview);
+ system_trash($trash_data, "DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
- db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision);
node_invoke_nodeapi($node, 'delete revision');
- drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))));
}
-
else {
drupal_set_message(t('Deletion failed. You tried to delete the current revision.'));
}
-
drupal_goto("node/$nid/revisions");
}
}
@@ -1872,59 +1956,119 @@ function node_form_submit($form_id, $edi
}
/**
- * Menu callback -- ask for confirmation of node deletion
+ * Menu callback -- deletes the node if no extra data is present
+ *
+ * @return
+ * If extra data is present, returns a confirmation form
*/
function node_delete_confirm() {
+
$edit = $_POST['edit'];
- $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
- $node = node_load($edit['nid']);
+ $nid = $edit['nid'] ? $edit['nid'] : arg(1);
+ $node = node_load($nid);
if (node_access('delete', $node)) {
$form['nid'] = array('#type' => 'value', '#value' => $node->nid);
- $output = confirm_form('node_delete_confirm', $form,
- t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
- $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action cannot be undone.'),
- t('Delete'), t('Cancel') );
- }
- return $output;
+ // Check for extra info from modules
+ $extras = node_invoke_nodeapi($node, 'delete pre');
+ $form['extras'] = $extras;
+
+ // No extras, just delete the node
+ if (!count($extras)) {
+ node_delete($node);
+ drupal_goto('node');
+ }
+ else {
+
+ // Cancel deletion if necessary, otherwise display confirm form
+ if (in_array(FALSE, $extras)) {
+ drupal_set_message(t('Deletion cancelled'), 'error');
+ drupal_goto('node/'. $node->nid .'/edit');
+ }
+ else {
+ return confirm_form('node_delete_confirm', $form,
+ t('Are you sure you want to delete %title?', array('%title' => theme('placeholder', $node->title))),
+ $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid, t('This action will move the
+ item to the trash'), t('Delete'), t('Cancel'));
+ }
+ }
+ }
}
/**
- * Execute node deletion
+ * Submits the form data for deletion.
+ *
+ * @param $form_id
+ * The unique form identifier.
+ * @param $form_values
+ * The constructed form array.
*/
function node_delete_confirm_submit($form_id, $form_values) {
+
if ($form_values['confirm']) {
- node_delete($form_values['nid']);
+ // Merge extra confirm data with node
+ $node = node_load($form_values['nid']);
+ $node = (array) $node;
+ $node = array_merge($node, $form_values);
+ $node = (object) $node;
+
+ node_delete($node);
+
drupal_goto();
}
}
/**
* Delete a node.
+ *
+ * @param $node
+ * The node to delete. Can pass a node object, node array, or nid.
*/
-function node_delete($nid) {
+function node_delete($node) {
- $node = node_load($nid);
+ // Load the node if nid or array is passed
+ if (is_numeric($node)) {
+ $node = node_load($node);
+ }
+ elseif (is_array($node)) {
+ $node = node_load($node['nid']);
+ }
- if (node_access('delete', $node)) {
- db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
- db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
+ // If the deletion package is just starting, add appropriate data to $node and $trash_data array
+ if (!$node->did) {
+ $node->did = next_delete_id();
+ $trash_data['rid'] = $node->nid;
+ $trash_data['name'] = 'node';
+ }
- // 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))));
+ // Finish building trash preview
+ $trash_data['did'] = $node->did;
+ $trash_data['title'] = $node->title;
+ $trash_data['description'] = $node->type;
+ if ($node->body) {
+ $preview['body'] = $node->body;
+ }
+ $trash_data['preview'] = $preview;
+
+ drupal_set_message(t('%title moved to the trash.', array('%title' => theme('placeholder', $node->title))));
+
+ system_trash($trash_data, 'DELETE FROM {node} WHERE nid = %d', $node->nid);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
+
+ // Call the node-specific callback (if any):
+ node_invoke($node, 'delete');
+ node_invoke_nodeapi($node, 'delete');
+
+ // Clear the cache so an anonymous poster can see the node being deleted.
+ cache_clear_all();
+
+ // Remove this node from the search index if needed.
+ if (function_exists('search_wipe')) {
+ search_wipe($node->nid, 'node');
}
+ watchdog('content', t('%type: deleted %title.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title))));
}
/**
=== modified file 'modules/path.module'
--- modules/path.module
+++ modules/path.module
@@ -58,7 +58,7 @@ function path_menu($may_cache) {
'access' => user_access('administer url aliases'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/path/delete', 'title' => t('delete alias'),
- 'callback' => 'path_admin_delete_confirm',
+ 'callback' => 'path_admin_delete',
'access' => user_access('administer url aliases'),
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/path/list', 'title' => t('list'),
@@ -96,47 +96,35 @@ function path_admin_edit($pid = 0) {
}
/**
- * Menu callback; confirms deleting an URL alias
+ * Menu callback; handles deletion of an URL alias.
**/
-function path_admin_delete_confirm($pid) {
- $path = path_load($pid);
- if (user_access('administer path aliases')) {
- $form['pid'] = array('#type' => 'value', '#value' => $pid);
- $output = confirm_form('path_admin_delete_confirm', $form,
- t('Are you sure you want to delete path alias %title?', array('%title' => theme('placeholder', $path['dst']))),
- $_GET['destination'] ? $_GET['destination'] : 'admin/path', t('This action cannot be undone.'),
- t('Delete'), t('Cancel') );
- }
+function path_admin_delete($pid = 0) {
+ $did = next_delete_id();
+ $result = db_fetch_object(db_query('SELECT src, dst FROM {url_alias} WHERE pid = %d', $pid));
- return $output;
-}
+ // Build trash preview
+ $preview = array('destination' => $result->dst);
-/**
- * Execute URL alias deletion
- **/
-function path_admin_delete_confirm_submit($form_id, $form_values) {
- if ($form_values['confirm']) {
- path_admin_delete($form_values['pid']);
- drupal_goto('admin/path');
- }
-}
-
-/**
- * Post-confirmation; delete an URL alias.
- */
-function path_admin_delete($pid = 0) {
- db_query('DELETE FROM {url_alias} WHERE pid = %d', $pid);
- drupal_set_message(t('The alias has been deleted.'));
-}
+ drupal_set_message(t('The alias has been moved to the trash.'));
+ $trash_data = array('did' => $did, 'rid' => $pid, 'name' => 'url alias', 'title' => $result->src, 'preview' => $preview);
+ system_trash($trash_data, '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) {
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, "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) {
@@ -235,7 +223,7 @@ function path_nodeapi(&$node, $op, $arg)
case 'delete':
$path = "node/$node->nid";
if (drupal_get_path_alias($path) != $path) {
- path_set_alias($path);
+ path_set_alias($path, NULL, NULL, $node);
}
break;
}
=== modified file 'modules/poll.module'
--- modules/poll.module
+++ modules/poll.module
@@ -84,10 +84,22 @@ function poll_cron() {
/**
* Implementation of hook_delete().
*/
-function poll_delete($node) {
- db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
- db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
- db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
+function poll_delete(&$node) {
+
+ // Poll trash preview
+ $preview = array();
+ if ($node->choice) {
+ foreach ($node->choice as $key => $array) {
+ $c++;
+ $preview["choice$c"] = $array['chtext'];
+ }
+ }
+
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, "DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
+ system_trash($trash_data, "DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
+ $trash_data = array_merge($trash_data, array('preview' => $preview));
+ system_trash($trash_data, "DELETE FROM {poll} WHERE nid = %d", $node->nid);
}
/**
=== modified file 'modules/profile.module'
--- modules/profile.module
+++ modules/profile.module
@@ -548,22 +548,21 @@ 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);
- db_query('DELETE FROM {profile_values} 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('Are you sure you want to delete the field %field?', array('%field' => theme('placeholder', $field->title))),
- 'admin/settings/profile',
- t('This action cannot be undone. If users have entered values into this field in their profile, these entries will also be deleted. If you want to keep the user-entered data, instead of deleting the field you may wish to edit this field and change it to a \'hidden profile field\' so that it may only be accessed by administrators.', array('%edit-field' => url('admin/settings/profile/edit/' . $fid))),
- 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));
+
+ // Build trash preview
+ $preview = array('explanation' => $result->explanation, 'category' => $result->category);
+
+ drupal_set_message(t('The field %field has been moved to the trash. If users have entered values into this field in their profile, these entries have also been moved to the trash. If you want to keep the user-entered data, instead of deleting the field you may wish to restore it and edit the field, changing it to a \'hidden profile field\' so that it may only be accessed by administrators.', array('%field' => theme('placeholder', $result->title), '%edit-field' => url('admin/settings/profile/edit/' . $fid))));
+
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, 'DELETE FROM {profile_values} WHERE fid = %d', $fid);
+ $trash_data = array_merge($trash_data, array('rid' => $fid, 'name' => 'profile', 'description' => 'profile field', 'title' => $result->title, 'preview' => $preview));
+ system_trash($trash_data, 'DELETE FROM {profile_fields} WHERE fid = %d', $fid);
+
+ cache_clear_all();
+ drupal_goto('admin/settings/profile');
}
function _profile_field_form($type, $edit = array()) {
=== modified file 'modules/statistics.module'
--- modules/statistics.module
+++ modules/statistics.module
@@ -477,7 +477,8 @@ 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);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {node_counter} WHERE nid = %d', $node->nid);
}
}
=== modified file 'modules/system.module'
--- modules/system.module
+++ modules/system.module
@@ -47,7 +47,7 @@ function system_help($section) {
* Implementation of hook_perm().
*/
function system_perm() {
- return array('administer site configuration', 'access administration pages', 'select different theme');
+ return array('administer site configuration', 'administer trash', 'access administration pages', 'select different theme');
}
/**
@@ -92,6 +92,7 @@ function system_elements() {
* Implementation of hook_menu().
*/
function system_menu($may_cache) {
+ global $user;
$items = array();
if ($may_cache) {
@@ -146,6 +147,14 @@ function system_menu($may_cache) {
}
$items[] = array('path' => 'admin/modules', 'title' => t('modules'),
'callback' => 'system_modules', 'access' => $access);
+
+ // Trash
+ $items[] = array('path' => 'admin/trash', 'title' => t('trash'),
+ 'callback' => 'system_admin_trash', 'access' => $user->uid != 0);
+ $items[] = array('path' => 'trash/preview', 'title' => t('preview'),
+ 'callback' => 'system_trash_preview', 'access' => $user->uid != 0, 'type' => MENU_CALLBACK);
+ $items[] = array('path' => 'trash/recover', 'title' => t('recover'),
+ 'callback' => 'system_trash_recover_single', 'access' => $user->uid != 0, 'type' => MENU_CALLBACK);
}
return $items;
@@ -1221,6 +1230,696 @@ function theme_search_box($form) {
}
/**
+ * Overview of trashbin items
+ */
+function system_admin_trash() {
+
+ global $user;
+ $op = $_POST['op'];
+ if ($op && ($op != t('Recover'))) {
+ return system_trash_delete_confirm();
+ }
+ $form = array();
+
+ // Determine if user can access only own trashbin items, or all trashbin items
+ $view_all = user_access('administer trash');
+ if ($view_all) {
+ $result = pager_query("SELECT * FROM {deleted} ORDER BY timestamp DESC", 25 , 0);
+ }
+ else {
+ $result = pager_query("SELECT * FROM {deleted} WHERE uid = %d ORDER BY timestamp DESC", 25 , 0, NULL, $user->uid);
+ }
+ $destination = drupal_get_destination();
+
+ // Build table item for each deleted package
+ while ($recover = db_fetch_object($result)) {
+ $preview = $recover->preview ? unserialize($recover->preview) : array();
+ $title = $recover->title ? $recover->title : (is_array($preview[0]) ? array_shift($preview[0]) : '[none]');
+ $description = $recover->description ? $recover->description : $recover->name;
+ $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($description));
+ $form['title'][$recover->did] = array('#type' => 'markup', '#value' => check_plain($title));
+ $form['preview'][$recover->did] = array('#type' => 'markup', '#value' => l(t('preview'), "trash/preview/$recover->did", array(), $destination));
+ }
+
+ // Build form only if items exist
+ if (isset($form['datetime'])) {
+ $form['dids'] = array('#type' => 'checkboxes', '#options' => $items);
+ $form['pager'] = array('#value' => theme('pager', NULL, 25, 0));
+ $form['buttons'] = array('#prefix' => '', '#suffix' => '
');
+ $form['buttons']['recover'] = array('#type' => 'submit', '#value' => t('Recover'));
+ if ($view_all) {
+ $form['buttons']['delete'] = array('#type' => 'submit', '#value' => t('Delete selected'));
+ $form['buttons']['delete_all'] = array('#type' => 'submit', '#value' => t('Delete all'));
+ }
+ }
+
+
+ return drupal_get_form('system_admin_trash', $form);
+}
+
+/**
+ * Theme the admin trash form
+ *
+ * @param $form
+ * Form array to be themed
+ * @return
+ * A themed HTML string representing the form.
+ */
+function theme_system_admin_trash($form) {
+
+ // Overview table:
+ if (is_array($form['datetime'])) {
+ $output = form_render($form['buttons']);
+ $header = array(NULL, t('Title'), t('Type'), t('Trashed'), t('Operations'));
+ foreach (element_children($form['datetime']) as $key) {
+ $row = array();
+ $row[] = form_render($form['dids'][$key]);
+ $row[] = form_render($form['title'][$key]);
+ $row[] = form_render($form['type'][$key]);
+ $row[] = form_render($form['datetime'][$key]);
+ $row[] = form_render($form['preview'][$key]);
+ $rows[] = $row;
+ }
+ $output .= theme('table', $header, $rows);
+
+ // Add pager if necessary
+ if ($form['pager']['#value']) {
+ $output .= form_render($form['pager']);
+ }
+ }
+ elseif (arg(1) != 'preview') {
+ $output = t('There are no items in your trash');
+ }
+
+ $output .= form_render($form);
+
+ return $output;
+}
+
+/**
+ * Move deleted items to deleted tables, and delete data from their original table
+ *
+ * This function manages the deletion of most drupal table data, in a way which allows
+ * subsequent restoration of the data. Deletion happens in 'packages', so that all data which
+ * is removed by any API calls, etc.is stored in the package. Multiple calls can be made to
+ * this function in a deletion cycle, and the relevant data/meta-data will be managed/summed
+ * for the package.
+ *
+ * @param $trash_data
+ * This is an associative array of metadata pertaining to the current deletion package, and
+ * supports the following keys:
+ *
+ * 'did' => The package id. This is a required key for all calls to system_trash. In
+ * If the call to system_trash starts the package deletion, a new did may be
+ * obtained by calling next_delete_id(). If the package deletion was initiated
+ * elsewhere, the should be passed into the appropiate object/array which the
+ * deletion function receives. Core API's follow this convention as well,
+ * ex. $node->did
+ *
+ * 'rid' => The root id of the package. Not required, but useful in situations where a
+ * package is primarily associated with a single core element. For example, for
+ * node deletions this would be the relevant nid, for user deletions the relevant
+ * uid, etc
+ *
+ * 'name' => The internal name of the deletion type, ex. 'node', 'user'. This should generally
+ * be passed in the call to system_trash which begins the package deletion, and is
+ * required to be passed at least once in the deletion of the package
+ *
+ * 'description' => The human-readable type description of the package, ex. 'blog', 'user account'.
+ * Not required. If not passed at any point in the package deletion, then
+ * the 'name' parameter will be used for display to the user.
+ *
+ * 'title' => The human-readable title of the package, ex. $node->title, $user->name. This is
+ * generally passed in the call to system_trash which begins package deletion. Not
+ * required, and will default to the first element of the 'preview' parameter if
+ * available, or [Untitle] if not.
+ *
+ * 'preview' => An associative array of items to display in the package preview, key = title,
+ * value = description. ex. 'body' => $node->body. Not required. Multiple
+ * previews can be passed in successive calls to system_trash for the same package,
+ * and they will be summed to form the final preview.
+ *
+ * @param $query
+ * The SQL query which will delete the data.
+ * @param $args
+ * Additional arguments to pass to SQL delete query
+ */
+function system_trash($trash_data, $query) {
+ global $user;
+ static $same_did;
+ static $rid;
+ static $name;
+ static $description;
+ static $title;
+ static $summed_preview;
+ static $extra;
+
+ // Grab the args for the query
+ $all_args = func_get_args();
+ if (count($all_args) > 2) {
+ $args = array_slice($all_args, 2);
+ }
+ else {
+ $args = array();
+ }
+
+ // Turn delete into select and get result
+ $delete = preg_replace('/DELETE.*?FROM/i', 'SELECT * FROM', $query);
+ $result = db_query($delete, $args);
+
+ // For rid, name, description, title give trash_data precedence, then existing value, then wipe value if did has changed
+ $did = $trash_data['did'];
+ $rid = $trash_data['rid'] ? $trash_data['rid'] : ($same_did == $did ? $rid : '');
+ $name = $trash_data['name'] ? $trash_data['name'] : ($same_did == $did ? $name : '');
+ $description = $trash_data['description'] ? $trash_data['description'] : ($same_did == $did ? $description : '');
+ $title = $trash_data['title'] ? $trash_data['title'] : ($same_did == $did ? $title : '');
+
+ // Grab table name and store
+ $table = preg_match('/^DELETE FROM \{([^}]+)/', $query, $m);
+ $table = $m[1];
+
+ // Loop through result inserting rows into deleted_data table
+ while ($data = db_fetch_array($result)) {
+ $row_id = db_next_id('{deleted_data}_row_id');
+ foreach ($data as $column => $value) {
+ $tid = db_next_id('{deleted_data}_tid');
+ db_query("INSERT INTO {deleted_data} VALUES(%d, %d, '%s', %d, '%s', '%s')", $tid, $did, $table, $row_id, $column, $value);
+ }
+ }
+
+ // Only one message for each did
+ if ($same_did != $did) {
+ drupal_set_message(t('You may %recover the item from the %trash.', array('%recover' => l(t('recover'), "trash/recover/$did"), '%trash' => l(t('trash'), "admin/trash"))));
+
+ // Allow modules to add extra metadata
+ $extra = serialize(module_invoke_all('system_trash', $trash_data));
+
+ $same_did = $did;
+ $summed_preview = array();
+
+ }
+ // Calculate new preview summary
+ if ($trash_data['preview']) {
+ $summed_preview[] = $trash_data['preview'];
+ }
+ $insert_preview = $summed_preview ? serialize($summed_preview) : '';
+
+ // Update metadata
+ if (db_num_rows(db_query('SELECT did FROM {deleted} WHERE did = %d', $did))) {
+ db_query("UPDATE {deleted} SET rid = '%s', name = '%s', description = '%s', title = '%s', preview = '%s', extra = '%s' WHERE did = %d",
+ $rid, $name, $description, $title, $insert_preview, $extra, $did);
+ }
+ else {
+ db_query("INSERT INTO {deleted} VALUES(%d, %d, '%s', '%s', '%s', '%s','%s', '%s', %d)", $did, $user->uid, $rid, $name, $description, $title, $insert_preview, $extra, time());
+ }
+ // Delete from original table
+ db_query($query, $args);
+}
+
+/**
+ * Menu callback, displays preview metadata for a deleted package
+ *
+ * @param $did
+ * Package id for which to display the preview.
+ * @return
+ * A themed HTML string representing the preview
+ */
+function system_trash_preview($did) {
+
+ global $user;
+ $op = $_POST['op'];
+ $insert = db_fetch_object(db_query('SELECT * FROM {deleted} WHERE did = %d', $did));
+
+ // Only allow trashbin admin and user who deleted package to view
+ $view_all = user_access('administer trash');
+ if ($view_all || $insert->uid == $user->uid) {
+
+ if ($op == t('Delete')) {
+ return system_trash_delete_confirm();
+ }
+
+ // Compose buttons and package id
+ $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'));
+ }
+ $form['dids']['#tree'] = TRUE;
+ $form['dids'][$did] = array('#type' => 'hidden', '#value' => $did);
+
+ $output .= drupal_get_form('system_trash_preview', $form, 'system_admin_trash');
+
+ // Grab preview data
+ $values = array();
+ $data = $insert->preview ? unserialize($insert->preview) : array();
+ $title = $insert->title ? $insert->title : (is_array($data[0]) ? array_shift($data[0]) : t('[Untitled]'));
+ $description = $insert->description ? $insert->description : $insert->name;
+
+ drupal_set_title(t('Preview: %title, Type: %type -- (trashed)', array('%title' => check_plain($title), '%type' => t($description))));
+ $header = array();
+ $rows = array();
+
+ // Loop through preview array and construct table rows
+ foreach ($data as $preview_section) {
+ foreach ($preview_section as $key => $value) {
+ $rows[] = array(''. t($key) . ': ', filter_xss($value));
+ }
+ $rows[] = array(array('data' => '', 'colspan' => 2));
+ }
+
+ $output .= '';
+ $output .= '- '. t('Trashed') .': '. format_date($insert->timestamp, 'small');
+ $output .= '
- '. t('Type') .': '. t($insert->name);
+ $output .= '
';
+ $output .= ''. t('Data') .':
';
+ $output .= theme('table', $header, $rows);;
+ return $output;
+ }
+ else {
+ drupal_access_denied();
+ }
+}
+
+/**
+ * Internal function -- restores the table data for the specified deleted packages
+ *
+ * @param $dids
+ * An array of package ids to restore.
+ * @return
+ * nid of a single recovered node, nothing in all other circumstances.
+ */
+function system_trash_recover($dids) {
+
+ $nid = NULL;
+ $single_item = (count($dids) == 1);
+
+ // Grab all data for all the specified packages, compose into a master array
+ $all_dids = implode(', ', $dids);
+ $result = db_query("SELECT * FROM {deleted_data} WHERE did IN(%s)", $all_dids);
+ while ($row = db_fetch_array($result)) {
+ $all_data[$row['did']][$row['row_id']][] = $row;
+ }
+
+ // Process data to elimate packages which fail dependency checks
+ if (isset($all_data)) {
+ $recover_data = system_trash_dependency_check($all_data);
+
+ // Recompose data array into table insert data, per row, and reinsert to orginal table
+ if ($recover_data) {
+ foreach ($recover_data as $did => $rows) {
+ foreach ($rows as $data) {
+ $table = $data[0]['dtable'];
+ $fields = array();
+ $values = array();
+ $data_values = array();
+ foreach ($data as $value) {
+ $fields[] = $value['dcolumn'];
+ $values[] = is_numeric($value['data']) ? '%d' : "'%s'";
+ $data_values[$value['dcolumn']] = $value['data'];
+ // Best guess at recovered node if present
+ if ($value['dcolumn'] == 'nid' && $single_item) {
+ $nid = $value['data'];
+ }
+ }
+ $query_fields = implode(', ', $fields);
+ $query_values = implode(', ', $values);
+ $args = array_merge(array($table), array($query_fields), $data_values);
+ db_query('INSERT INTO {%s} (%s) VALUES('. $query_values .')', $args);
+ }
+ $delete_dids[] = $did;
+
+ // Allow other modules to process recovery data for this package
+ $trash_data = db_fetch_array(db_query('SELECT * FROM {deleted} WHERE did = %d', $did));
+ $trash_data['extra'] = $trash_data['extra'] ? unserialize($trash_data['extra']) : array();
+ module_invoke_all('system_trash_recover', $trash_data);
+ }
+
+ // Remove restored packages from deleted tables
+ $delete_dids_query = implode(', ', $delete_dids);
+ db_query('DELETE FROM {deleted} WHERE did IN(%s)', $delete_dids_query);
+ db_query('DELETE FROM {deleted_data} WHERE did IN(%s)', $did);
+ }
+ }
+ return $nid;
+}
+
+/**
+ * Checks dependencies for all pachages being recovered, and eliminates those which fail
+ *
+ * Function is recursive, eliminating packages which fail dependency checks and repeatedly
+ * calling itself until only packages which pass checks remain
+ *
+ * @param $recover_data
+ * An array of package data for potential recovery.
+ * @return
+ * The $recover_data array with all failed packages pruned.
+ */
+function system_trash_dependency_check($recover_data) {
+
+ // Run dependency checks per package, then per table row
+ $dids = array_keys($recover_data);
+ foreach ($recover_data as $did => $rows) {
+ foreach ($rows as $row => $data) {
+ $table = $data[0]['dtable'];
+
+ // If package fails checks, remove from the $recover_data array and call the function again
+ if (!system_trash_check_row_dependency($table, $data, $dids)) {
+ $recover = FALSE;
+ $package = db_fetch_object(db_query('SELECT preview FROM {deleted} WHERE did = %d', $did));
+ $preview = $package->preview ? unserialize($package->preview) : array();
+ if ($preview) {
+ $title = array_shift($preview[0]);
+ }
+ else {
+ $title = t('[untitled]');
+ }
+ drupal_set_message(t('%title cannot be recovered due to dependency errors. Check the errors and recover any data this
+ package depends on before attempting recovery.', array('%title' => theme('placeholder', $title))), 'error');
+ unset($recover_data[$data[0]['did']]);
+ return system_trash_dependency_check($recover_data);
+ }
+ }
+ }
+ return $recover_data;
+}
+
+/**
+ * Check dependencies for a single row of table data
+ *
+ * All relevant dependencies are checked for the table in question
+ *
+ * @param $table
+ * The table for which depedencies will be checked.
+ * @param $data
+ * An associative array containing the row data to be tested, key = column name, value = column data.
+ * @param $dids
+ * An array containing all package id's being recovered
+ * @return
+ * TRUE if the row passes dependency checks, FALSE otherwise.
+ */
+function system_trash_check_row_dependency($table, $data, $dids) {
+
+ $return = TRUE;
+ $dependencies = system_get_dependencies($table);
+ foreach ($dependencies as $dependency) {
+ $value = NULL;
+
+ // Check for optional dependency bypass conditions
+ $dependency[3] = isset($dependency[3]) ? $dependency[3] : array();
+
+ // Find the relevant dependent column, and pull the value
+ foreach ($data as $array) {
+ if ($array['dcolumn'] == $dependency[0]) {
+ $value = $array['data'];
+ }
+ }
+
+ // Error if no value is found, as it's required to check the dependency
+ if (!isset($value)) {
+ $return = FALSE;
+ drupal_set_message(t('Dependency error: no matching key %key in table %table. Dependency of table %dependency', array('%key' => $dependency[0],
+ '%table' => $table, '%dependency' => $dependency[2])), 'error');
+ }
+
+ // Check existing table data for the dependent data, then check all packages being restored, then check for bypass exceptions
+ else {
+ if (!system_check_dependency($dependency, $value) && !system_check_package_dependencies($dependency, $value, $dids) && !in_array($value, $dependency[3])) {
+ $return = FALSE;
+ drupal_set_message(t("Dependency error: value '%value' for key '%key' missing in table %dependency", array('%value' => $value,
+ '%key' => $dependency[1], '%dependency' => $dependency[2])), 'error');
+ }
+ }
+ }
+ return $return;
+}
+
+/**
+ * Output a confirmation form
+ *
+ * This function outputs a complete form for confirming an action. A link is
+ * offered to go back to the item that is being changed in case the user changes
+ * his/her mind.
+ *
+ * You should use $GLOBALS['values']['edit'][$name] (where $name is usually 'confirm') to
+ * check if the confirmation was successful.
+ *
+ * @param $dependency
+ * An array of column dependency data for the row being checked.
+ * @param $value
+ * The row value of the dependent column being checked.
+ * @param $dids
+ * An array of package id's being recovered.
+ * @return
+ * TRUE value if the dependency passes, FALSE otherwise.
+ */
+function system_check_package_dependencies($dependency, $value, $dids) {
+
+ $field = $dependency[1];
+ $table = $dependency[2];
+ $dids_query = implode(', ', $dids);
+ return db_num_rows(db_query("SELECT * FROM {deleted_data} WHERE dtable = '%s' AND dcolumn = '%s' AND data = '%s' AND did IN(%s)",
+ $table, $field, $value, $dids_query));
+
+}
+
+/**
+ * Menu callback. Recovers the specified single package
+ *
+ * @param $did
+ * The package id to be recovered.
+ */
+function system_trash_recover_single($did) {
+
+ // Only allow trash admins and the user who deleted the node to recover
+ if (user_access('administer trash') || $user->uid == db_result(db_query('SELECT uid FROM {deleted} WHERE did = %d', $did))) {
+ $nid = system_trash_recover(array($did));
+ drupal_set_message(t('The item has been recovered'));
+ if ($nid = db_result(db_query('SELECT nid FROM {node} WHERE nid = %d', $nid))) {
+ drupal_goto("node/$nid");
+ }
+ else {
+ drupal_goto();
+ }
+ }
+ else {
+ drupal_access_denied();
+ }
+}
+
+/**
+ * Submits the form data for trashbin recovery.
+ *
+ * @param $form_id
+ * The unique form identifier.
+ * @param $form_values
+ * The constructed form array.
+ */
+function system_admin_trash_submit($form_id, $form_values) {
+
+ $dids = $form_values['dids'] ? array_keys($form_values['dids'], !0) : array();
+ if ((count($dids) == 0)) {
+ drupal_set_message(t('No items were selected. Select items in order to perform the operation'));
+ drupal_goto('admin/trash');
+ }
+
+ // Provide a link to the node if a single node has been recovered.
+ $nid = system_trash_recover($dids);
+ $message = $nid ? t('The %item has been recovered', array('%item' => l(t('item'), "node/$nid"))) : t('The items have been recovered');
+
+ drupal_set_message($message);
+ drupal_goto('admin/trash');
+}
+
+/**
+ * Internal function. Permanently delete the specified package
+ *
+ * @param $did
+ * Package id to be deleted.
+ */
+function system_trash_delete($did) {
+
+ $trash_data = db_fetch_array(db_query('SELECT * FROM {deleted} WHERE did = %d', $did));
+
+ // Allow other modules to process custom data prior to the deletion.
+ $trash_data['extra'] = $trash_data['extra'] ? unserialize($trash_data['extra']) : array();
+ module_invoke_all('system_trash_delete', $trash_data);
+
+ db_query('DELETE FROM {deleted} WHERE did = %d', $did);
+ db_query('DELETE FROM {deleted_data} WHERE did = %d', $did);
+}
+
+/**
+ * Submits the form data for trashbin permanent deletion.
+ *
+ * @param $form_id
+ * The unique form identifier.
+ * @param $form_values
+ * The constructed form array.
+ */
+function system_trash_delete_confirm_submit($form_id, $form_values) {
+
+ // Specific packages were selected, so delete those
+ if (isset($form_values['dids'])) {
+ foreach ($form_values['dids'] as $did) {
+ system_trash_delete($did);
+ }
+ }
+
+ // 'Delete all' was selected
+ else {
+ $dids = db_query('SELECT did FROM {deleted}');
+ while ($did = db_fetch_object($dids)) {
+ $did = $did->did;
+ system_trash_delete($did);
+ }
+ }
+
+ drupal_set_message(t('Permanent delete successful'));
+ drupal_goto('admin/trash');
+}
+
+/**
+ * Internal function. Prints confirmation form for permanent deletion
+ *
+ * @return
+ * An HTML string representing the confirmation form.
+ */
+function system_trash_delete_confirm() {
+
+ $dids = $_POST['edit']['dids'] ? array_keys($_POST['edit']['dids'], !0) : array();
+ if ((count($dids) == 0) && $_POST['op'] == t('Delete selected')) {
+ drupal_set_message(t('No items were selected. Select items in order to perform the operation'));
+ drupal_goto('admin/trash');
+ }
+
+ $form['dids']['#tree'] = TRUE;
+ foreach ($dids as $did) {
+ $form['dids'][$did] = array('#type' => 'hidden', '#value' => $did);
+ }
+
+ $output = confirm_form('system_trash_delete_confirm', $form,
+ t('Are you sure you want to permanently delete these items?'),
+ 'admin/trash', t('This operation cannot be undone.'),
+ t('Delete'), t('Cancel'));
+ return $output;
+}
+
+/**
+ * Pulls the next did from the sequences table.
+ */
+function next_delete_id() {
+ return db_next_id('{deleted}_did');
+}
+
+/**
+ * Implementation of hook_table_dependencies.
+ *
+ * @return
+ * An array of table dependencies for drupal core.
+ */
+function system_table_dependencies() {
+ $dependencies = array();
+ $dependencies['accesslog'] = array(array('sid', 'sid', 'sessions'), array('uid', 'uid', 'users'));
+ $dependencies['aggregator_category_feed'] = array(array('cid', 'cid', 'aggregator_category'));
+ // NEED TO FIGURE OUT THE OTHER AGGREGATOR TABLE RELATIONSHIPS
+ $dependencies['authmap'] = array(array('uid', 'uid', 'users')); //AID RELATED TO ANYTHING?
+ $dependencies['book'] = array(array('vid', 'vid', 'node_revisions'), array('nid', 'nid', 'node'));
+ $dependencies['comments'] = array(array('uid', 'uid', 'users'), array('nid', 'nid', 'node'), array('pid', 'cid', 'comments', array(0)));
+ $dependencies['files'] = array(array('nid', 'nid', 'node'), array('vid', 'vid', 'node_revisions'));
+ $dependencies['filters'] = array(array('format', 'format', 'filter_formats'));
+ $dependencies['forum'] = array(array('nid', 'nid', 'node'), array('vid', 'vid', 'node_revisions'), array('tid', 'tid', 'term_data'));
+ $dependencies['history'] = array(array('uid', 'uid', 'users'), array('nid', 'nid', 'node_revisions'));
+ // LOCALES TABLES NEED ENTERED HERE
+ $dependencies['menu'] = array(array('pid', 'mid', 'menu'));
+ $dependencies['moderation_roles'] = array(array('rid', 'rid', 'role'), array('mid', 'mid', 'moderation_votes'));
+ $dependencies['node'] = array(array('vid', 'vid', 'node_revisions'), array('uid', 'uid', 'users'));
+ $dependencies['node_access'] = array(array('nid', 'nid', 'node')); // GID RELATED TO ANYTHING?
+ $dependencies['node_comment_statistics'] = array(array('nid', 'nid', 'node'), array('last_comment_uid', 'uid', 'users'));
+ $dependencies['node_counter'] = array(array('nid', 'nid', 'node'));
+ $dependencies['node_revisions'] = array(array('nid', 'nid', 'node'), array('uid', 'uid', 'users'));
+ $dependencies['permission'] = array(array('rid', 'rid', 'role'));
+ $dependencies['poll'] = array(array('nid', 'nid', 'node'));
+ $dependencies['poll_choices'] = array(array('nid', 'nid', 'node'));
+ $dependencies['profile_values'] = array(array('fid', 'fid', 'profile_fields'), array('uid', 'uid', 'users'));
+ // SEARCH TABLES HERE
+ $dependencies['sessions'] = array(array('uid', 'uid', 'users')); // SID RELATED TO ANYTHING?
+ $dependencies['term_data'] = array(array('vid', 'vid', 'vocabulary'));
+ $dependencies['term_hierarchy'] = array(array('tid', 'tid', 'term_data'), array('parent', 'tid', 'term_hierarchy'));
+ $dependencies['term_node'] = array(array('nid', 'nid', 'node'), array('tid', 'tid', 'term_data'));
+ $dependencies['term_relation'] = array(array('tid1', 'tid', 'term_data'), array('tid2', 'tid', 'term_data'));
+ $dependencies['term_synonym'] = array(array('tid', 'tid', 'term_data'));
+ $dependencies['users_roles'] = array(array('uid', 'uid', 'users'), array('rid', 'rid', 'role'));
+ $dependencies['vocabulary_node_types'] = array(array('vid', 'vid', 'vocabulary')); //TYPE RELATED TO ANYTHING?
+ $dependencies['watchdog'] = array(array('uid', 'uid', 'users'));
+
+ return $dependencies;
+}
+
+/**
+ * Builds an array of table dependencies for all enabled modules.
+ *
+ * @return
+ * An array of table dependencies.
+ */
+function system_build_dependencies() {
+ static $dependencies;
+
+ if (!isset($dependencies)) {
+ // Build dependencies array
+ $dependencies = module_invoke_all('table_dependencies');
+ }
+ return $dependencies;
+}
+
+/**
+ * Submits the form data for trashbin recovery.
+ *
+ * @param $table
+ * The table for which to pull dependencies.
+ * @return
+ * A dependency array for the specified table.
+ */
+function system_get_dependencies($table) {
+
+ $dependencies = system_build_dependencies();
+ if (array_key_exists($table, $dependencies)) {
+ return $dependencies[$table];
+ }
+ else {
+ return array();
+ }
+}
+
+/**
+ * Checks a single table dependency.
+ *
+ * @param $dependency
+ * A single array element to check -- pulled from a table's dependency array generated by system_get_dependencies.
+ * @param $value
+ * The value to check for in the dependency. This is the value that must be present in the table being
+ * checked in order for the row to pass this check.
+ * @return
+ * TRUE if the dependent data is found, FALSE otherwise.
+ */
+function system_check_dependency($dependency, $value) {
+
+ $field = $dependency[1];
+ $table = $dependency[2];
+
+ if (is_numeric($value)) {
+ $result = db_num_rows(db_query('SELECT * FROM {%s} WHERE %s = %d', $table, $field, $value));
+ }
+ else {
+ $result = db_num_rows(db_query("SELECT * FROM {%s} WHERE %s = '%s'", $table, $field, $value));
+ }
+
+ return $result;
+}
+
+/**
* Output a confirmation form
*
* This function outputs a complete form for confirming an action. A link is
=== modified file 'modules/taxonomy.module'
--- modules/taxonomy.module
+++ modules/taxonomy.module
@@ -171,13 +171,22 @@ function taxonomy_save_vocabulary(&$edit
function taxonomy_del_vocabulary($vid) {
$vocabulary = (array) taxonomy_get_vocabulary($vid);
- db_query('DELETE FROM {vocabulary} WHERE vid = %d', $vid);
- db_query('DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
+ $did = next_delete_id();
+
+ // Build trash preview
+ $preview = array('description' => $vocabulary['description']);
+
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, 'DELETE FROM {vocabulary_node_types} WHERE vid = %d', $vid);
+ $trash_data = array_merge($trash_data, array('rid' => $vid, 'name' => 'vocabulary', 'description' => 'vocabulary term', 'title' => $vocabulary['name'], 'preview' => $preview));
+ system_trash($trash_data, 'DELETE FROM {vocabulary} WHERE vid = %d', $vid);
+
$result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
while ($term = db_fetch_object($result)) {
- taxonomy_del_term($term->tid);
+ taxonomy_del_term($term->tid, $did);
}
+ $vocabulary['did'] = $did;
module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
cache_clear_all();
@@ -185,20 +194,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);
@@ -315,7 +310,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();
@@ -333,36 +335,43 @@ function taxonomy_del_term($tid) {
$term = (array) taxonomy_get_term($tid);
- db_query('DELETE FROM {term_data} WHERE tid = %d', $tid);
- db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
- db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
- db_query('DELETE FROM {term_synonym} WHERE tid = %d', $tid);
- db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);
+ // Build initial package data if this is the beginning of package deletion
+ if (!$recurse && !$vocab_did) {
+ $preview = array('description' => $term['description']);
+ $trash_data = array('did' => $did, 'rid' => $term['vid'], 'name' => 'taxonomy term', 'description' => 'category term', 'title' => $term['name'], 'preview' => $preview);
+ $recurse = 1;
+ }
+
+ // Add to the package preview
+ else {
+ $count++;
+ $preview = array();
+ if ($term['name']) {
+ $preview['term'. $count] = $term['name'];
+ }
+ if ($term['description']) {
+ $preview['description'. $count] = $term['description'];
+ }
+ $trash_data = array('did' => $did, 'preview' => $preview);
+ }
+
+ drupal_set_message(t('Moved term %name to the trash.', array('%name' => theme('placeholder', $term['name']))));
+
+ system_trash($trash_data, 'DELETE FROM {term_data} WHERE tid = %d', $tid);
+ system_trash($trash_data, 'DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
+ system_trash($trash_data, 'DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $tid, $tid);
+ system_trash($trash_data, 'DELETE FROM {term_synonym} WHERE tid = %d', $tid);
+ system_trash($trash_data, '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.
*/
@@ -591,7 +600,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.
@@ -652,13 +661,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') {
@@ -1019,7 +1021,8 @@ function taxonomy_nodeapi($node, $op, $a
taxonomy_node_save($node->nid, $node->taxonomy);
break;
case 'delete':
- taxonomy_node_delete($node->nid);
+ $trash_data = array('did' => $node->did);
+ system_trash($trash_data, 'DELETE FROM {term_node} WHERE nid = %d', $node->nid);
break;
case 'validate':
taxonomy_node_validate($node);
@@ -1136,20 +1139,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)) {
@@ -1160,7 +1152,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 the trash.', array('%name' => theme('placeholder', $deleted_name))));
break;
}
}
=== modified file 'modules/upload.module'
--- modules/upload.module
+++ modules/upload.module
@@ -317,17 +317,30 @@ function upload_nodeapi(&$node, $op, $ar
}
break;
case 'delete revision':
- $node->files = upload_load($node);
- foreach ($node->files as $file) {
- // Check any other revisions pointing to file first.
+ $file_info = upload_load($node);
+ foreach ($file_info as $file) {
+
+ // Add preview for only those files associated with this single revision
if( db_result(db_query("SELECT COUNT(fid) FROM {files} WHERE fid = %d", $file->fid)) == 1 ) {
- file_delete($file->filepath);
+ $i++;
+ $preview['file'. $i] = $file->filename;
}
}
- db_query("DELETE FROM {files} WHERE vid = %d", $node->vid);
+
+ $trash_data = array('did' => $node->did, 'preview' => $preview);
+ system_trash($trash_data, "DELETE FROM {files} WHERE vid = %d", $node->vid);
break;
case 'delete':
- upload_delete($node);
+ $file_info = upload_load($node);
+
+ // Build trash preview
+ foreach ($file_info as $file) {
+ $i++;
+ $preview['file'. $i] = $file->filename;
+ }
+
+ $trash_data = array('did' => $node->did, 'preview' => $preview);
+ system_trash($trash_data, "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;
@@ -426,14 +439,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) {
$header = array(t('Delete'), t('List'), t('Description'), t('Size'));
$rows = array();
@@ -546,3 +551,105 @@ function upload_js() {
print drupal_call_js('window.parent.iframeHandler', $output);
exit;
}
+
+/**
+ * Implementation of hook_system_trash
+ *
+ * Composes metadata for file attachments related to nodes that are being sent to trash.
+ * Renames the files to make them unavailable.
+ *
+ * @param $trash_data
+ * Metadata for the package being deleted.
+ * @return
+ * An array with a 'files' element, which contains upload metadata for the attachments
+ * to the node in question.
+ */
+function upload_system_trash($trash_data) {
+
+ $rid = $trash_data['rid'];
+
+ // Only invoke if it's a node being deleted, and nid is known
+ if (($trash_data['name'] == 'node') && isset($rid)) {
+ $node = node_load($rid);
+ $file_info = upload_load($node);
+
+ // Pull a random string for appending to the file name--this prevents direct access to the file
+ $key = substr(md5(uniqid(rand(), TRUE)), 0, 26);
+
+ // Add each attachment's filepatch to the metadata
+ foreach ($file_info as $fid => $file) {
+ $filepaths[$fid]['original_path'] = $file->filepath;
+ $filepaths[$fid]['renamed_path'] = $file->filepath . $key;
+ $filepaths[$fid]['original_name'] = $file->filename;
+ $filepaths[$fid]['renamed_name'] = $file->filename . $key;
+
+ //Rename file
+ if(!file_move(&$file->filepath, $filepaths[$fid]['renamed_path'], FILE_EXISTS_ERROR)) {
+ drupal_set_message(t('Attachment %file could not be renamed--original name kept', array('%file' => $filepaths[$fid]['original_name'])), 'error');
+ $filepaths[$fid]['renamed_path'] = $filepaths[$fid]['original_path'];
+ $filepaths[$fid]['renamed_name'] = $filepaths[$fid]['original_name'];
+ }
+ }
+
+ $recover['files'] = $filepaths;
+ return $recover;
+ }
+}
+
+/**
+ * Implementation of hook_system_trash_recover
+ *
+ * Processes upload metadata for the package being recovered. Specifically it restores the original file name
+ * to all node attachments
+ *
+ * @param $trash_data
+ * Metadata for the package being recovered.
+ */
+function upload_system_trash_recover($trash_data) {
+
+ $rid = $trash_data['rid'];
+
+ // Only invoke if it's a node being recovered, and nid is known
+ if (($trash_data['name'] == 'node') && isset($rid)) {
+
+ $files = $trash_data['extra']['files'];
+
+ if ($files) {
+ foreach ($files as $file) {
+
+ //Restore original filename
+ if(!file_move($file['renamed_path'], $file['original_path'])) {
+ drupal_set_message(t('Attachment %file could not be renamed to original name -- %name kept', array('%file' => $file['original_name'],
+ '%name' => $file['renamed_name'])), 'error');
+
+ // Can't restore, so update files table
+ db_query("UPDATE {files} SET filename = '%s', filepath = '%s' WHERE nid = %d AND filename = '%s'", $file['renamed_name'],
+ $file['renamed_path'], $rid, $file['original_name']);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_system_trash_delete
+ *
+ * Permanently deletes any file attachments related to the package being deleted.
+ *
+ * @param $trash_data
+ * Metadata for the package being deleted.
+ */
+function upload_system_trash_delete($trash_data) {
+
+ $files = $trash_data['extra']['files'];
+
+ if ($files) {
+ foreach ($files as $fid => $filepath) {
+
+ // Check any other revisions pointing to file first.
+ if (db_result(db_query("SELECT COUNT(fid) FROM {files} WHERE fid = %d", $fid)) == 0) {
+ file_delete($filepath);
+ }
+ }
+ }
+}
=== modified file 'modules/user.module'
--- modules/user.module
+++ modules/user.module
@@ -1263,19 +1263,25 @@ function user_edit($category = 'account'
$edit = $_POST['op'] ? $_POST['edit'] : (array)$account;
if (arg(2) == 'delete') {
- if ($edit['confirm']) {
- db_query('DELETE FROM {users} WHERE uid = %d', $account->uid);
- db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
- db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
- db_query('DELETE FROM {authmap} WHERE uid = %d', $account->uid);
- watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE);
- drupal_set_message(t('The account has been deleted.'));
- module_invoke_all('user', 'delete', $edit, $account);
- drupal_goto('admin/user');
- }
- else {
- return confirm_form('user_confirm_delete', $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();
+
+ // Build trash preview
+ $preview = array('email' => $account->mail);
+
+ drupal_set_message(t('The account has been moved to the trash.'));
+
+ db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid);
+
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, 'DELETE FROM {users_roles} WHERE uid = %d', $account->uid);
+ system_trash($trash_data, 'DELETE FROM {authmap} WHERE uid = %d', $account->uid);
+ $trash_data = array_merge($trash_data, array('rid' => $account->uid, 'name' => 'user', 'description' => 'user account', 'title' => $account->name, 'preview' => $preview));
+ system_trash($trash_data, 'DELETE FROM {users} WHERE uid = %d', $account->uid);
+
+ watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE);
+ $account->did = $did;
+ module_invoke_all('user', 'delete', $edit, $account);
+ drupal_goto('admin/user');
}
else if ($_POST['op'] == t('Delete')) {
if ($_REQUEST['destination']) {
@@ -1473,21 +1479,16 @@ function user_admin_access_add($mask = N
function user_admin_access_delete($aid = 0) {
$access_types = array('user' => t('username'), 'mail' => t('e-mail'));
$edit = db_fetch_object(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid));
+ $did = next_delete_id();
- $form = array();
- $form['aid'] = array('#type' => 'hidden', '#value' => $aid);
- $output = confirm_form('user_admin_access_delete_confirm', $form,
- t('Are you sure you want to delete the %type rule for %rule?', array('%type' => $access_types[$edit->type], '%rule' => theme('placeholder', $edit->mask))),
- 'admin/access/rules',
- t('This action cannot be undone.'),
- t('Delete'),
- t('Cancel'));
- return $output;
-}
+ // Build trash preview
+ $preview = array('rule' => $edit->mask);
-function user_admin_access_delete_confirm_submit($form_id, $edit) {
- db_query('DELETE FROM {access} WHERE aid = %d', $edit['aid']);
drupal_set_message(t('The access rule has been deleted.'));
+
+ $trash_data = array('did' => $did, 'rid' => $aid, 'name' => 'access rule', 'description' => 'user access rule', 'title' => $access_types[$edit->type], 'preview' => $preview);
+ system_trash($trash_data, 'DELETE FROM {access} WHERE aid = %d', $aid);
+
drupal_goto('admin/access/rules');
}
@@ -1716,8 +1717,15 @@ function user_admin_role() {
}
}
else if ($op == t('Delete role')) {
- db_query('DELETE FROM {role} WHERE rid = %d', $id);
- db_query('DELETE FROM {permission} WHERE rid = %d', $id);
+ $did = next_delete_id();
+ $title = db_result(db_query('SELECT name FROM {role} WHERE rid = %d', $id));
+
+ drupal_set_message(t('The role has been moved to the trash. All users with only this role have been moved to the authenticated users pool.'));
+
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, 'DELETE FROM {permission} WHERE rid = %d', $id);
+ $trash_data = array_merge($trash_data, array('rid' => $id, 'name' => 'role', 'description' => 'user role', 'title' => $title));
+ system_trash($trash_data, 'DELETE FROM {role} 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);
@@ -1728,10 +1736,10 @@ function user_admin_role() {
}
if ($uid) {
- db_query('DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid));
+ $trash_data = array('did' => $did);
+ system_trash($trash_data, 'DELETE FROM {users_roles} WHERE rid = %d AND uid IN (%s)', $id, implode(', ', $uid));
}
- drupal_set_message(t('The role has been deleted.'));
drupal_goto('admin/access/roles');
}
else if ($op == t('Add role')) {
=== modified file 'update.php'
--- update.php
+++ update.php
@@ -359,6 +359,7 @@ function update_selection_page() {
}
function update_update_page() {
+
// Set the installed version so updates start at the correct place.
$_SESSION['update_remaining'] = array();
foreach ($_POST['edit']['start'] as $module => $version) {
@@ -463,6 +464,9 @@ function update_progress_page_nojs() {
}
function update_finished_page() {
+
+ // The deleted table is cleared here to prevent stale data from being reinserted between db updates
+ db_query('DELETE FROM {deleted}');
drupal_set_title('Drupal database update');
// NOTE: we can't use l() here because the URL would point to 'update.php?q=admin'.
$links[] = 'main page';