=== modified file 'includes/common.inc' --- includes/common.inc 2007-06-15 21:27:09 +0000 +++ includes/common.inc 2007-06-17 18:24:27 +0000 @@ -2841,6 +2841,469 @@ function drupal_common_themes() { } /** + * Used to begin a new deletion package. A package is set of deletion + * queries associated with a particular kind of deletion -- for example, + * all the queries associated with a node deletion. Most often it will + * not be necessary to start a new package, as most non-core deletions will + * already be part of a package initiated by core. Once a package has + * been started, all metadata, callbacks, and queries are added to the package. + * A package is complete when either a new package is started, or when a + * confirm or execute command is given. + * + * @param $type + * The deletion type for the package, ex. 'node', 'user', 'comment'. + * @param $id + * A unique identifier for the package. By convention this is the primary + * key of the 'root' deletion, ex. the nid for node type deletions, the uid + * for user type deletions, etc. + */ +function drupal_delete_initiate($type, $id) { + drupal_delete('new package', array('type' => $type, 'id' => $id)); +} + +/** + * Pass a deletion query into a deletion package. + * + * @param $id + * A unique string identifier for the kind of deletion being performed, ex. 'node'. + * @param $query + * The query to be passed, followed by any additional arguments for escaped values. + * ex. drupal_delete_add_query('comments', 'DELETE FROM {comments} WHERE nid = %d', array('nid' => $node->nid)); + * The additional arguments can be passed in any fashion that db_query() accepts. + */ +function drupal_delete_add_query($id, $query) { + $all_args = func_get_args(); + array_unshift($all_args, 'query'); + call_user_func_array('drupal_delete', $all_args); +} + +/** + * Initiates the confirmation cycle. This command fully builds all packages + * for deletion, and returns a confirm form array containing any injected messages + * which can be used to print a confirmation screen. + * + * @param $confirm + * An associative array with the following key/value pairs: + * 'form' => Optional. An array representing the form elements to pass to the confirm form. + * 'question' => Optional. The question for the confirm form. + * 'path' => Optional. The cancellation path for the confirm form. + * + * Also, any valid options from the $options argument of confirm_form() may be passed, + * and they will be passed through to the confirm form. + * ex. drupal_delete( + * array( + * 'form' => $form, + * 'question' => t('Are you sure you want to delete these items?'), + * 'path' => 'admin/content/node', + * 'yes' => t('Delete all'), + * 'destination' => 'admin/content/node', + * ) + * ); + */ +function drupal_delete_confirm($confirm) { + return drupal_delete('confirm', '', $confirm); +} + +/** + * Initiates the deletion of all constructed packages. Confirmation messages + * are bypassed, but abort messages are respected. + */ +function drupal_delete_execute() { + drupal_delete('execute'); +} + +/** + * Register post-deletion callback functions for a package. The functions are called after the package + * has been deleted. Useful for miscellaneous cleanup, user messages, etc. + * + * @param $callbacks + * An associative array of callback functions, key = name of function, + * value = an array of arguments to pass to the function. + * ex. drupal_delete( + * array( + * 'node_delete_post' => array($node->nid, $node->title, $node->type), + * ) + * ); + */ +function drupal_delete_add_callback($callbacks) { + drupal_delete('callback', '', $callbacks); +} + +/** + * Pass metadata related to the deletion or package to the API. This is made + * available to all hooks called during the deletion cycle. + * + * @param $metadata + * An associative array of metadata. + * ex. drupal_delete( + * array( + * 'comment_messages' => $messages, + * ) + * ); + */ +function drupal_delete_add_metadata($metadata) { + drupal_delete('metadata', '', $metadata); +} + +/** + * Pass in a package-specific set of form elements, to be displayed in the + * confirm form. Use this in multiple deletion scenarios where the confirm + * information shouldn't be displayed if the package is aborted. + * + * @param $elements + * An array representing the package-specific form elements to pass to the confirm form. + * ex. drupal_delete_add_form_elements( + * array( + * "node_$node->nid" => array( + * '#value' => check_plain($node->title), + * '#prefix' => '
  • ', + * '#suffix' => "
  • \n", + * ), + * ) + * ); + */ +function drupal_delete_add_form_elements($elements) { + drupal_delete('form', '', $elements); +} + +/** + * Create/build/execute deletion packages. + * + * Note that this function should not be called directly, but through the following helper functions: + * + * drupal_delete_initiate() + * drupal_delete_add_query() + * drupal_delete_confirm() + * drupal_delete_execute() + * drupal_delete_add_callback() + * drupal_delete_add_metadata() + * drupal_delete_add_form_elements() + * + * @param $op + * An operation to be performed -- one of the following values: + * + * - 'query': Pass a deletion query to the API. + * + * - 'new package': Used to begin a new deletion package. A package is set of deletion + * queries associated with a particular kind of deletion -- for example, + * all the queries associated with a node deletion. Most often it will + * not be necessary to start a new package, as most non-core deletions will + * already be part of a package initiated by core. Once a package has + * been started, all calls to drupal_delete() containing the 'query', + * 'metadata', and 'callback' operations are added to the package. A + * package is complete when either a new package is started, or + * when drupal_delete() is called with the 'confirm' or 'execute' + * operations. + * + * - 'confirm': Initiates the confirmation cycle. This command fully builds all packages + * for deletion, and returns a confirm form array containing any injected messages + * which can be used to print a confirmation screen. + * + * - 'form': Pass in a package-specific set of form elements, to be displayed in the + * confirm form. Use this in multiple deletion scenarios where the confirm + * information shouldn't be displayed if the package is aborted. + * + * - 'execute': Initiates the deletion of all constructed packages. Confirmation messages + * are bypassed, but abort messages are respected. + * + * - 'callback': Register a callback function. The function is called after the package + * has been deleted. Useful for miscellaneous cleanup, user messages, etc. + * + * - 'metadata': Pass metadata related to the deletion or package to the API. This is made + * available to all hooks called during the deletion cycle. + * + * @param $id + * A unique string identifier for the deletion being performed. + * + * Additional arguments are passed to the function, depending on the operation to be + * performed: + * + * - 'query': The query to be passed, followed by any additional arguments for escaped + * values. + * ex. drupal_delete('query', 'comment', 'DELETE FROM {comments} WHERE nid = %d', array('nid' => $node->nid)); + * The additional arguments can be passed in any fashion that db_query() accepts. + * + * - 'new package': An associative array with the following key/value pairs: + * 'type' => The deletion type for the package, ex. 'node', 'user', 'comment'. + * 'id' => A unique identifier for the package. By convention this is the primary + * key of the 'root' deletion, ex. the nid for node type deletions, the uid + * for user type deletions, etc. + * + * - 'confirm': An associative array with the following key/value pairs: + * 'form' => Optional. An array representing the form elements to pass to the confirm form. + * 'question' => Optional. The question for the confirm form. + * 'path' => Optional. The cancellation path for the confirm form. + * + * Also, any valid options from the $options argument of confirm_form() may be passed, + * and they will be passed through to the confirm form. + * ex. drupal_delete('confirm', '', + * array( + * 'form' => $form, + * 'question' => t('Are you sure you want to delete these items?'), + * 'path' => 'admin/content/node', + * 'yes' => t('Delete all'), + * 'destination' => 'admin/content/node', + * ) + * ); + * + * - 'form': An array representing the package-specific form elements to pass to the confirm form. + * ex. drupal_delete('form', '', + * array( + * "node_$node->nid" => array( + * '#value' => check_plain($node->title), + * '#prefix' => '
  • ', + * '#suffix' => "
  • \n", + * ), + * ) + * ); + * + * - 'execute': No arguments required. + * + * - 'callback': An associative array of callback functions, key = name of function, + * value = an array of arguments to pass to the function. + * ex. drupal_delete('callback', '', + * array( + * 'node_delete_post' => array($node->nid, $node->title, $node->type), + * ) + * ); + * + * - 'metadata': An associative array of metadata. + * ex. drupal_delete('metadata', '', + * array( + * 'comment_messages' => $messages + * ) + * ); + */ +function drupal_delete($op, $id = '') { + static $delete_type = NULL, + $delete_id = NULL, + $deletion_data = array(), + $built = NULL, + $form = array(); + + $confirm = NULL; + $execute = NULL; + $destination = FALSE; + + // Process additional arguments. + $all_args = func_get_args(); + if (count($all_args) > 2) { + // Get any additional arguments. + $args = array_slice($all_args, 2); + // Shift off query string if present. + if ($op == 'query') { + $query = array_shift($args); + } + // Clean up args passed as an array. + if (is_array($args[0])) { + $args = $args[0]; + } + } + else { + $args = array(); + } + + switch ($op) { + // Query to add to a package + case 'query': + // Add the information for this deletion into the package array. + $deletion_data[$delete_type][$delete_id][$id][] = array('query' => $query, 'args' => $args); + break; + // New package -- mark it as such and get the next deletion ID. + case 'new package': + $delete_type = $id['type']; + $delete_id = $id['id']; + $deletion_data[$delete_type][$delete_id] = array( + 'metadata' => array(), + 'callback' => array(), + 'form' => array(), + ); + break; + // Confirm all packages. + case 'confirm': + // Set execute switch and destination if bypass is enabled. + if (variable_get('bypass_delete_confirmation', 0)) { + $execute = TRUE; + $destination = isset($args['destination']) ? $args['destination'] : NULL; + } + else { + $confirm = TRUE; + } + break; + // Add package-specific form array pieces, callbacks, metadata. + case 'form': + case 'callback': + case 'metadata': + $deletion_data[$delete_type][$delete_id][$op] += $args; + break; + // Execute all packages. + case 'execute': + $execute = TRUE; + break; + } + + // Allow modules to inject confirm/abort data. + if (isset($confirm) || isset($execute)) { + // $built is kept as a static and checked to avoid calling this code twice + // when the package is executed. + if (!isset($built)) { + $built = TRUE; + + // Main confirm form. + if (isset($args['form'])) { + $form = $args['form']; + } + foreach ($deletion_data as $type => $deletion_info) { + // Reset $delete_type to the type being checked. + $delete_type = $type; + foreach ($deletion_info as $package_id => $package_info) { + $package_confirm_data = array(); + // Reset $delete_id to the package to be passed. + $delete_id = $package_id; + // Call hook_delete_pre() once for each package. + foreach (module_implements('delete_pre') as $module) { + $confirm_data = module_invoke($module, 'delete_pre', $type, $package_id, $package_info); + // Check for an aborted package. + if (isset($confirm_data['abort'])) { + // Set abort message. + if (isset($confirm_data['abort']['message'])) { + drupal_set_message($confirm_data['abort']['message'], 'error'); + } + // Set abort destination. + if (isset($confirm_data['abort']['destination'])) { + $abort_destination = $confirm_data['abort']['destination']; + } + // Remove the package. + unset($deletion_data[$type][$package_id]); + } + else { + // Add elements to confirm form data for the package. + $package_confirm_data = array_merge_recursive($package_confirm_data, $confirm_data); + } + } + // Add in package-specific confirm form elements if the package still exists. + if (isset($deletion_data[$type][$package_id])) { + $form = array_merge_recursive($form, $package_confirm_data); + if (!empty($package_info['form'])) { + $form = array_merge_recursive($form, $package_info['form']); + } + } + } + } + if (isset($confirm)) { + // Check to make sure there's at least one valid package left. + $count = FALSE; + foreach ($deletion_data as $type => $deletion_info) { + foreach ($deletion_info as $package_id => $package_info) { + // Package should contain more than just 'form', 'callback', and 'metadata' + // keys if it contains any actual deletion data -- otherwise it was just a + // metadata-only package. + if (count($package_info) > 3) { + $count = TRUE; + break; + } + } + } + // Generate the confirm form. + if ($count) { + $question = isset($args['question']) ? $args['question'] : t('Delete the item?'); + $path = isset($args['path']) ? $args['path'] : 'node'; + unset($args['question'], $args['path']); + $args['name'] = 'delete'; + // Submit handler - triggers execute operation for the API. + $form['#submit'] = array('delete_confirm_submit'); + return confirm_form($form, $question, $path, $args); + } + // No packages left after aborts -- go to an abort destination if it exists. + elseif (isset($abort_destination)) { + drupal_goto($abort_destination); + } + // Fallback to cancel path. + elseif (isset($args['path'])) { + drupal_goto($args['path']); + } + // Last fallback, front page. + else { + drupal_goto(''); + } + } + } + } + + // Execute all constructed packages. + if (isset($execute)) { + + // Explicitly reset the package information. + $delete_type = NULL; + $delete_id = NULL; + $package_data = $deletion_data; + $deletion_data = array(); + $built = NULL; + + // Execute the deletion of all packages. + _drupal_delete_execute($package_data, $destination); + } +} + +/** + * Executes all database deletions in all packages of a single page call. + * + * @param $package_data + * The complete array of deletion data for all packages. + * @param $destination + * A destination for operations that bypass the confirm step. + */ +function _drupal_delete_execute($package_data, $destination = NULL) { + foreach ($package_data as $type => $package) { + foreach ($package as $package_id => $package_info) { + // Allow modules to perform any operations just prior to the deletion of the package. + drupal_alter('delete', $type, $package_id, $package_info); + // Extract callbacks and metadata. + $callbacks = $package_info['callback']; + unset($package_info['callback'], $package_info['metadata'], $package_info['form']); + // Perform the deletions. + foreach ($package_info as $deletions) { + foreach ($deletions as $deletion) { + db_query($deletion['query'], $deletion['args']); + } + } + // Execute post-deletion callbacks. + foreach ($callbacks as $function => $args) { + if (function_exists($function)) { + call_user_func_array($function, $args); + } + } + } + } + // Redirect for confirmation bypass. + drupal_redirect($destination); +} + +/** + * Generates a drupal_goto() based on the value of $goto. + * + * @param $goto + * Can be any of the following values: + * - A string representing a valid Drupal path. + * - An array containing arguments to pass to drupal_goto(). + * - NULL to redirect to $_GET['q']. + * - FALSE to bypass the redirection. + */ +function drupal_redirect($goto) { + if ($goto !== FALSE) { + if (isset($goto)) { + if (is_array($goto)) { + call_user_func_array('drupal_goto', $goto); + } + else { + drupal_goto($goto); + } + } + drupal_goto($_GET['q']); + } +} + +/** * @ingroup schemaapi * @{ */ === modified file 'includes/form.inc' --- includes/form.inc 2007-06-17 15:19:56 +0000 +++ includes/form.inc 2007-06-17 15:24:14 +0000 @@ -485,17 +485,7 @@ function drupal_redirect_form($form, $re if ($goto !== FALSE && isset($form['#redirect'])) { $goto = $form['#redirect']; } - if (!isset($goto) || ($goto !== FALSE)) { - if (isset($goto)) { - if (is_array($goto)) { - call_user_func_array('drupal_goto', $goto); - } - else { - drupal_goto($goto); - } - } - drupal_goto($_GET['q']); - } + drupal_redirect(isset($goto) ? $goto : NULL); } /** === modified file 'modules/book/book.module' --- modules/book/book.module 2007-06-11 17:37:11 +0000 +++ modules/book/book.module 2007-06-17 16:30:34 +0000 @@ -466,10 +466,10 @@ function book_nodeapi(&$node, $op, $teas } break; case 'delete revision': - db_query('DELETE FROM {book} WHERE vid = %d', $node->vid); + drupal_delete_add_query('book', 'DELETE FROM {book} WHERE vid = %d', $node->vid); break; case 'delete': - db_query('DELETE FROM {book} WHERE nid = %d', $node->nid); + drupal_delete_add_query('book', 'DELETE FROM {book} WHERE nid = %d', $node->nid); break; } } === modified file 'modules/comment/comment.module' --- modules/comment/comment.module 2007-06-15 21:27:13 +0000 +++ modules/comment/comment.module 2007-06-17 16:35:28 +0000 @@ -232,7 +232,8 @@ function comment_menu() { $items['comment/delete'] = array( 'title' => 'Delete comment', - 'page callback' => 'comment_delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('comment_delete', 2), 'access arguments' => array('administer comments'), 'type' => MENU_CALLBACK, ); @@ -474,8 +475,8 @@ function comment_nodeapi(&$node, $op, $a break; case 'delete': - db_query('DELETE FROM {comments} WHERE nid = %d', $node->nid); - db_query('DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid); + drupal_delete_add_query('comments', 'DELETE FROM {comments} WHERE nid = %d', $node->nid); + drupal_delete_add_query('node_comment_statistics', 'DELETE FROM {node_comment_statistics} WHERE nid = %d', $node->nid); break; case 'update index': @@ -1093,50 +1094,44 @@ function comment_render($node, $cid = 0) /** * Menu callback; delete a comment. */ -function comment_delete($cid = NULL) { +function comment_delete(&$form_state, $cid = NULL) { $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid)); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; - $output = ''; - if (is_object($comment) && is_numeric($comment->cid)) { - $output = drupal_get_form('comment_confirm_delete', $comment); + drupal_delete_initiate('comment', $comment->cid); + drupal_delete_add_callback( + array( + 'comment_delete_post' => array($comment), + // Clear the cache so an anonymous poster can see the node being deleted. + 'cache_clear_all' => array(), + ) + ); + + // Delete comment and its replies. + _comment_delete_thread($comment); + + return drupal_delete_confirm( + array( + 'question' => t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)), + 'path' => 'node/'. $comment->nid, + 'description' => t('Any replies to this comment will be lost. This action cannot be undone.'), + 'destination' => 'node/'. $comment->nid, + ) + ); } else { - drupal_set_message(t('The comment no longer exists.')); + drupal_set_message(t('The comment no longer exists.'), 'error'); + drupal_goto(''); } - - return $output; -} - -function comment_confirm_delete(&$form_state, $comment) { - $form = array(); - $form['#comment'] = $comment; - return confirm_form( - $form, - t('Are you sure you want to delete the comment %title?', array('%title' => $comment->subject)), - 'node/'. $comment->nid, - t('Any replies to this comment will be lost. This action cannot be undone.'), - t('Delete'), - t('Cancel'), - 'comment_confirm_delete'); } -function comment_confirm_delete_submit($form, &$form_state) { - drupal_set_message(t('The comment and all its replies have been deleted.')); +function comment_delete_post($comment) { - $comment = $form['#comment']; - - // Delete comment and its replies. - _comment_delete_thread($comment); + drupal_set_message(t('The comment %subject and all its replies have been deleted.', array('%subject' => $comment->subject))); + watchdog('content', 'Comment: deleted %subject and all its replies.', array('%subject' => $comment->subject)); _comment_update_node_statistics($comment->nid); - - // Clear the cache so an anonymous user sees that his comment was deleted. - cache_clear_all(); - - $form_state['redirect'] = "node/$comment->nid"; - return; } /** @@ -1291,45 +1286,78 @@ function theme_comment_admin_overview($f function comment_multiple_delete_confirm(&$form_state) { $edit = $form_state['post']; - $form['comments'] = array('#prefix' => '', '#tree' => TRUE); + $form['comments']['#tree'] = TRUE; + $form['prefix'] = array('#value' => ''); + $form['operation'] = array( + '#type' => 'hidden', + '#value' => 'delete' + ); + + // New package for the main admin callback. + drupal_delete_initiate('comment', 'admin'); + + // Add the main callback for the mass deletion last. + drupal_delete_add_callback(array('comment_multiple_delete_confirm_post' => array())); if (!$comment_counter) { drupal_set_message(t('There do not appear to be any comments to delete or your selected comment was deleted by another administrator.')); drupal_goto('admin/content/comment'); } else { - return confirm_form($form, - t('Are you sure you want to delete these comments and all their children?'), - 'admin/content/comment', t('This action cannot be undone.'), - t('Delete comments'), t('Cancel')); + return drupal_delete_confirm( + array( + 'form' => $form, + 'question' => t('Are you sure you want to delete these comments and all their children?'), + 'path' => 'admin/content/comment', + 'yes' => t('Delete all'), + 'destination' => 'admin/content/comment', + ) + ); } } /** - * Perform the actual comment deletion. + * Post deletion callback for multiple comment deletion. */ -function comment_multiple_delete_confirm_submit($form, &$form_state) { - if ($form_state['values']['confirm']) { - foreach ($form_state['values']['comments'] as $cid => $value) { - $comment = _comment_load($cid); - _comment_delete_thread($comment); - _comment_update_node_statistics($comment->nid); - } +function comment_multiple_delete_confirm_post() { cache_clear_all(); drupal_set_message(t('The comments have been deleted.')); - } - drupal_goto('admin/content/comment'); } /** @@ -1892,8 +1920,7 @@ function _comment_delete_thread($comment } // Delete the comment: - db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid); - watchdog('content', 'Comment: deleted %subject.', array('%subject' => $comment->subject)); + drupal_delete_add_query('comments', 'DELETE FROM {comments} WHERE cid = %d', $comment->cid); comment_invoke_comment($comment, 'delete'); === modified file 'modules/forum/forum.module' --- modules/forum/forum.module 2007-06-04 16:34:10 +0000 +++ modules/forum/forum.module 2007-06-17 16:36:24 +0000 @@ -169,7 +169,7 @@ function forum_perm() { function forum_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'delete revision': - db_query('DELETE FROM {forum} WHERE vid = %d', $node->vid); + drupal_delete_add_query('forum', 'DELETE FROM {forum} WHERE vid = %d', $node->vid); break; } } @@ -453,7 +453,7 @@ function forum_insert($node) { * Implementation of hook_delete(). */ function forum_delete(&$node) { - db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid); + drupal_delete_add_query('forum', 'DELETE FROM {forum} WHERE nid = %d', $node->nid); } /** === modified file 'modules/node/node.module' --- modules/node/node.module 2007-06-17 15:19:57 +0000 +++ modules/node/node.module 2007-06-17 16:42:58 +0000 @@ -1383,7 +1383,7 @@ function node_filters() { } $filters['type'] = array('title' => t('type'), 'options' => node_get_types('names')); - + // The taxonomy filter if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) { $filters['category'] = array('title' => t('category'), 'options' => $taxonomy); @@ -1685,29 +1685,67 @@ function theme_node_admin_nodes($form) { function node_multiple_delete_confirm(&$form_state) { $edit = $form_state['post']; - $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['nodes']['#tree'] = TRUE; + $form['prefix'] = array('#value' => ''); + + $form['operation'] = array( + '#type' => 'hidden', + '#value' => 'delete', + ); - return confirm_form($form, - t('Are you sure you want to delete these items?'), - 'admin/content/node', t('This action cannot be undone.'), - t('Delete all'), t('Cancel')); + // New package for the main admin callback. + drupal_delete_initiate('node', 'admin'); + + // Add the main callback for the mass deletion last. + drupal_delete_add_callback(array('node_multiple_delete_confirm_post' => array())); + + return drupal_delete_confirm( + array( + 'form' => $form, + 'question' => t('Are you sure you want to delete these items?'), + 'path' => 'admin/content/node', + 'yes' => t('Delete all'), + 'destination' => 'admin/content/node', + ) + ); } -function node_multiple_delete_confirm_submit($form, &$form_state) { - if ($form_state['values']['confirm']) { - foreach ($form_state['values']['nodes'] as $nid => $value) { - node_delete($nid); - } - drupal_set_message(t('The items have been deleted.')); - } - $form_state['redirect'] = 'admin/content/node'; - return; +function node_multiple_delete_confirm_post() { + // Clear the cache so an anonymous poster can see the nodes being deleted. + cache_clear_all(); + drupal_set_message(t('The items have been deleted.')); } /** @@ -1789,32 +1827,44 @@ function node_revision_revert($nid, $rev * revision is deleted. */ function node_revision_delete($nid, $revision) { - if (user_access('administer nodes')) { - $node = node_load($nid); - if (node_access('delete', $node)) { - // Don't delete the current revision - if ($revision != $node->vid) { - $node = node_load($nid, $revision); - - db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); - node_invoke_nodeapi($node, 'delete revision'); - drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision))); - watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node->type, '%title' => $node->title, '%revision' => $revision)); - } + $node = node_load($nid); - else { - drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); - } - if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $nid)) > 1) { - drupal_goto("node/$nid/revisions"); - } - else { - drupal_goto("node/$nid"); - } + if (user_access('administer nodes') && node_access('delete', $node)) { + + // Don't delete the current revision + if ($revision != $node->vid) { + $node = node_load($nid, $revision); + + drupal_delete_initiate('node_revision', $revision); + drupal_delete_add_callback(array('node_revision_delete_post' => array($node, $revision))); + drupal_delete_add_query('node_revisions', 'DELETE FROM {node_revisions} WHERE vid = %d', $revision); + node_invoke_nodeapi($node, 'delete revision'); + drupal_delete_execute(); } + else { + drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); + } + } + else { + drupal_access_denied(); } +} - drupal_access_denied(); +/** + * Post revision deletion opertations. + */ +function node_revision_delete_post($node, $revision) { + + drupal_set_message(t('Deleted %title revision %revision.', array('%title' => $node->title, '%revision' => $revision))); + watchdog('content', '@type: deleted %title revision %revision.', array('@type' => t($node->type), '%title' => $node->title, '%revision' => $revision)); + + + if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) > 1) { + drupal_goto("node/$node->nid/revisions"); + } + else { + drupal_goto("node/$node->nid"); + } } /** @@ -2371,25 +2421,25 @@ function node_form_submit($form, &$form_ * Menu callback -- ask for confirmation of node deletion */ function node_delete_confirm(&$form_state, $node) { - $form['nid'] = array('#type' => 'value', '#value' => $node->nid); - return confirm_form($form, - t('Are you sure you want to delete %title?', array('%title' => $node->title)), - isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid, - t('This action cannot be undone.'), - t('Delete'), t('Cancel')); -} + drupal_delete_initiate('node', $node->nid); + drupal_delete_add_callback( + array( + 'node_delete_post' => array($node->nid, $node->title, $node->type), + // Clear the cache so an anonymous poster can see the node being deleted. + 'cache_clear_all' => array(), + ) + ); -/** - * Execute node deletion - */ -function node_delete_confirm_submit($form, &$form_state) { - if ($form_state['values']['confirm']) { - node_delete($form_state['values']['nid']); - } + node_delete($node->nid, FALSE); - $form_state['redirect'] = ''; - return; + return drupal_delete_confirm( + array( + 'question' => t('Are you sure you want to delete %title?', array('%title' => $node->title)), + 'path' => isset($_GET['destination']) ? $_GET['destination'] : 'node/'. $node->nid, + 'destination' => isset($_GET['destination']) ? $_GET['destination'] : '', + ) + ); } /** @@ -2400,23 +2450,23 @@ function node_delete($nid) { $node = node_load($nid); if (node_access('delete', $node)) { - db_query('DELETE FROM {node} WHERE nid = %d', $node->nid); - db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); + drupal_delete_add_query('node', 'DELETE FROM {node} WHERE nid = %d', $node->nid); + drupal_delete_add_query('node_revision', '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(); +function node_delete_post($nid, $title, $type) { - // Remove this node from the search index if needed. - if (function_exists('search_wipe')) { - search_wipe($node->nid, 'node'); - } - drupal_set_message(t('%title has been deleted.', array('%title' => $node->title))); - watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title)); + // Remove this node from the search index if needed. + if (function_exists('search_wipe')) { + search_wipe($nid, 'node'); } + drupal_set_message(t('%title has been deleted.', array('%title' => $title))); + watchdog('content', '@type: deleted %title.', array('@type' => t($type), '%title' => $title)); } /** === modified file 'modules/path/path.module' --- modules/path/path.module 2007-06-04 14:15:53 +0000 +++ modules/path/path.module 2007-06-17 16:44:07 +0000 @@ -127,12 +127,12 @@ function path_admin_delete($pid = 0) { function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = '') { if ($path && !$alias) { // Delete based on path - db_query("DELETE FROM {url_alias} WHERE src = '%s' AND language = '%s'", $path, $language); + drupal_delete_add_query('url_alias', "DELETE FROM {url_alias} WHERE src = '%s' AND language = '%s'", $path, $language); drupal_clear_path_cache(); } else if (!$path && $alias) { // Delete based on alias - db_query("DELETE FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $alias, $language); + drupal_delete_add_query('url_alias', "DELETE FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $alias, $language); drupal_clear_path_cache(); } else if ($path && $alias) { === modified file 'modules/poll/poll.module' --- modules/poll/poll.module 2007-06-04 14:15:51 +0000 +++ modules/poll/poll.module 2007-06-17 16:44:50 +0000 @@ -91,9 +91,9 @@ function poll_cron() { * Implementation of hook_delete(). */ function poll_delete($node) { - db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid); - db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid); - db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid); + drupal_delete_add_query('poll', 'DELETE FROM {poll} WHERE nid = %d', $node->nid); + drupal_delete_add_query('poll_choices', 'DELETE FROM {poll_choices} WHERE nid = %d', $node->nid); + drupal_delete_add_query('poll_votes', 'DELETE FROM {poll_votes} WHERE nid = %d', $node->nid); } /** === modified file 'modules/statistics/statistics.module' --- modules/statistics/statistics.module 2007-05-26 05:23:49 +0000 +++ modules/statistics/statistics.module 2007-06-17 16:45:11 +0000 @@ -553,7 +553,7 @@ function statistics_nodeapi(&$node, $op, switch ($op) { case 'delete': // clean up statistics table when node is deleted - db_query('DELETE FROM {node_counter} WHERE nid = %d', $node->nid); + drupal_delete_add_query('node_counter', 'DELETE FROM {node_counter} WHERE nid = %d', $node->nid); } } === modified file 'modules/system/system.module' --- modules/system/system.module 2007-06-17 15:19:55 +0000 +++ modules/system/system.module 2007-06-17 15:24:09 +0000 @@ -2420,21 +2420,26 @@ function system_node_type($op, $info) { * block foo?"). * @param $path * The page to go to if the user denies the action. - * Can be either a drupal path, or an array with the keys 'path', 'query', 'fragment'. - * @param $description - * Additional text to display (defaults to "This action cannot be undone."). - * @param $yes - * A caption for the button which confirms the action (e.g. "Delete", - * "Replace", ...). - * @param $no - * A caption for the link which denies the action (e.g. "Cancel"). - * @param $name - * The internal name used to refer to the confirmation item. + * Can be either a Drupal path, or an array with the keys 'path', 'query', 'fragment'. + * @param $options + * An associative array of options, with the following key/value pairs: + * 'description' => Additional text to display. + * Default is "This action cannot be undone". + * 'yes' => A caption for the button which confirms the action (e.g. "Confirm", + * "Replace", ...). Default is "Delete". + * 'no' => A caption for the link which denies the action (e.g. "Cancel"). + * Default is "Cancel". + * 'name' => The internal name used to refer to the confirmation item. + * Default is "confirm". + * 'destination' => A destination page to go to after form submission -- the value can + * be of any form of the $goto argument accepted by drupal_redirect(). + * * @return * The form. */ -function confirm_form($form, $question, $path, $description = NULL, $yes = NULL, $no = NULL, $name = 'confirm') { - $description = isset($description) ? $description : t('This action cannot be undone.'); +function confirm_form($form, $question, $path, $options = array()) { + $description = isset($options['description']) ? $options['description'] : t('This action cannot be undone.'); + $name = isset($options['name']) ? $options['name'] : 'confirm'; // Prepare cancel link $query = $fragment = NULL; @@ -2443,25 +2448,38 @@ function confirm_form($form, $question, $fragment = isset($path['fragment']) ? $path['fragment'] : NULL; $path = isset($path['path']) ? $path['path'] : NULL; } - $cancel = l($no ? $no : t('Cancel'), $path, array('query' => $query, 'fragment' => $fragment)); + $cancel = l(isset($options['no']) ? $options['no'] : t('Cancel'), $path, array('query' => $query, 'fragment' => $fragment)); drupal_set_title($question); - // Confirm form fails duplication check, as the form values rarely change -- so skip it. - $form['#skip_duplicate_check'] = TRUE; - $form['#attributes'] = array('class' => 'confirmation'); $form['description'] = array('#value' => $description); $form[$name] = array('#type' => 'hidden', '#value' => 1); + if (isset($options['destination'])) { + $form['destination'] = array('#type' => 'value', '#value' => $options['destination']); + } $form['actions'] = array('#prefix' => '
    ', '#suffix' => '
    '); - $form['actions']['submit'] = array('#type' => 'submit', '#value' => $yes ? $yes : t('Confirm')); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => isset($options['yes']) ? $options['yes'] : t('Delete')); $form['actions']['cancel'] = array('#value' => $cancel); + $form['#theme'] = 'confirm_form'; return $form; } /** + * Executes confirmation pages for the Deletion API. + */ +function delete_confirm_submit($form, &$form_state) { + if ($form_state['values']['delete']) { + drupal_delete_execute(); + } + if (isset($form_state['values']['destination'])) { + $form_state['redirect'] = $form_state['values']['destination']; + } +} + +/** * Determine if a user is in compact mode. */ function system_admin_compact_mode() { === modified file 'modules/taxonomy/taxonomy.module' --- modules/taxonomy/taxonomy.module 2007-06-17 15:19:57 +0000 +++ modules/taxonomy/taxonomy.module 2007-06-17 16:46:06 +0000 @@ -876,14 +876,14 @@ function taxonomy_node_save($node, $term * Remove associations of a node to its terms. */ function taxonomy_node_delete($node) { - db_query('DELETE FROM {term_node} WHERE nid = %d', $node->nid); + drupal_delete_add_query('term_node', 'DELETE FROM {term_node} WHERE nid = %d', $node->nid); } /** * Remove associations of a node to its terms. */ function taxonomy_node_delete_revision($node) { - db_query('DELETE FROM {term_node} WHERE vid = %d', $node->vid); + drupal_delete_add_query('term_node', 'DELETE FROM {term_node} WHERE vid = %d', $node->vid); } /** === modified file 'modules/upload/upload.module' --- modules/upload/upload.module 2007-06-17 15:19:57 +0000 +++ modules/upload/upload.module 2007-06-17 16:47:11 +0000 @@ -575,31 +575,44 @@ function upload_delete($node) { } foreach ($files as $fid => $file) { - // Delete all files associated with the node - db_query('DELETE FROM {files} WHERE fid = %d', $fid); - file_delete($file->filepath); + // Delete all file revision information associated with the node + drupal_delete_add_query('files', 'DELETE FROM {files} WHERE fid = %d', $fid); } - // Delete all file revision information associated with the node - db_query('DELETE FROM {upload} WHERE nid = %d', $node->nid); + // Delete all files associated with the node + drupal_delete_add_query('upload', 'DELETE FROM {upload} WHERE nid = %d', $node->nid); + + // Register a callback to delete the files. + drupal_delete_add_callback(array('upload_delete_post' => array($files))); } function upload_delete_revision($node) { if (is_array($node->files)) { + $files = array(); foreach ($node->files as $file) { // Check if the file will be used after this revision is deleted $count = db_result(db_query('SELECT COUNT(fid) FROM {upload} WHERE fid = %d', $file->fid)); // if the file won't be used, delete it if ($count < 2) { - db_query('DELETE FROM {files} WHERE fid = %d', $file->fid); - file_delete($file->filepath); + drupal_delete_add_query('files', 'DELETE FROM {files} WHERE fid = %d', $file->fid); + $files[$file->fid] = $file; } } } - // delete the revision - db_query('DELETE FROM {upload} WHERE vid = %d', $node->vid); + // Delete the revision. + drupal_delete_add_query('upload', 'DELETE FROM {upload} WHERE vid = %d', $node->vid); + + // Register a callback to delete the files. + drupal_delete_add_callback(array('upload_delete_post' => array($files))); +} + +function upload_delete_post($files) { + foreach ($files as $file) { + // Delete all files associated with the node or revision. + file_delete($file->filepath); + } } function _upload_form($node) {