Index: modules/book/book.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.admin.inc,v retrieving revision 1.17 diff -u -p -r1.17 book.admin.inc --- modules/book/book.admin.inc 11 Jan 2009 21:19:17 -0000 1.17 +++ modules/book/book.admin.inc 29 Jan 2009 20:35:15 -0000 @@ -12,10 +12,16 @@ function book_admin_overview() { $rows = array(); foreach (book_get_books() as $book) { - $rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/" . $book['nid'])); + $row = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/" . $book['nid'])); + if (user_access('bypass node access')) { + $row[] = l(t('delete book'), 'admin/content/book/delete/' . $book['nid']); + } + $rows[] = $row; } $headers = array(t('Book'), t('Operations')); - + if (user_access('bypass node access')) { + $headers[] = t('Delete'); + } return theme('table', $headers, $rows); } @@ -251,3 +257,86 @@ function theme_book_admin_table($form) { return theme('table', $header, $rows, array('id' => 'book-outline')); } +/** + * Menu callback. Ask for confirmation of book deletion. + */ +function book_delete_confirm(&$form_state, $node) { + $form['bid'] = array( + '#type' => 'value', + '#value' => $node->nid, + ); + + return confirm_form($form, + t('Are you sure you want to delete the entire book %title?', array('%title' => $node->title)), 'admin/content/book', + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Execute full book deletion using batch processing. + */ +function book_delete_confirm_submit($form, &$form_state) { + if ($form_state['values']['confirm']) { + $bid = $form_state['values']['bid']; + $batch = array( + 'title' => t('Deleting book'), + 'operations' => array( + array('book_delete', array($bid)), + ), + 'finished' => '_book_delete_finished', + 'file' => drupal_get_path('module', 'book') . '/book.admin.inc', + ); + batch_set($batch); + batch_process(); + } +} + +/** + * Batch processing callback. Delete an entire book 5 nodes at a time. + */ +function book_delete($bid, &$context) { + if (!isset($context['sandbox']['progress'])) { + $context['sandbox']['max'] = db_result(db_query("SELECT COUNT(nid) FROM {book} WHERE bid = %d", $bid)); + $context['sandbox']['progress'] = 0; + $context['sandbox']['highest_nid'] = 0; + $context['sandbox']['bid'] = $bid; + } + + // Delete 5 nodes at a time. + $result = db_query_range("SELECT nid FROM {book} WHERE bid = %d AND nid > %d AND nid <> %d ORDER BY nid ASC", $context['sandbox']['bid'], $context['sandbox']['highest_nid'], $context['sandbox']['bid'], 0, 5); + while ($nid = db_result($result)) { + node_delete($nid); + // Update our progress information. + $context['sandbox']['progress']++; + $context['sandbox']['highest_nid'] = $nid; + } + + // Delete the top book node last. + if ($context['sandbox']['progress'] == $context['sandbox']['max'] - 1) { + node_delete($context['sandbox']['bid']); + $context['sandbox']['progress']++; + } + + // Multistep processing : report progress. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } +} + +/** + * Book delete batch 'finished' callback. + */ +function _book_delete_finished($success, $results, $operations) { + if ($success) { + drupal_set_message(t('The book has been deleted.')); + } + else { + drupal_set_message(t('An error occurred and processing did not complete.'), 'error'); + $message = format_plural(count($results), '1 item successfully deleted:', '@count items successfully deleted:'); + $message .= theme('item_list', $results); + drupal_set_message($message); + } + drupal_goto('admin/content/book'); +} \ No newline at end of file Index: modules/book/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.module,v retrieving revision 1.485 diff -u -p -r1.485 book.module --- modules/book/book.module 26 Jan 2009 14:08:42 -0000 1.485 +++ modules/book/book.module 29 Jan 2009 20:35:15 -0000 @@ -159,6 +159,13 @@ function book_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['admin/content/book/delete/%node'] = array( + 'title' => 'Delete Book', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('book_delete_confirm', 4), + 'access callback' => '_book_delete_access', + 'type' => MENU_CALLBACK, + ); return $items; } @@ -178,6 +185,13 @@ function _book_outline_remove_access($no } /** + * Menu item access callback - determine if the user can delete entire books. + */ +function _book_delete_access() { + return user_access('administer book outlines') && user_access('bypass node access'); +} + +/** * Implementation of hook_init(). */ function book_init() { @@ -1216,4 +1230,4 @@ function book_menu_subtree_data($item) { } return $tree[$cid]; -} +} \ No newline at end of file Index: modules/book/book.test =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.test,v retrieving revision 1.7 diff -u -p -r1.7 book.test --- modules/book/book.test 23 Dec 2008 14:32:08 -0000 1.7 +++ modules/book/book.test 29 Jan 2009 20:35:15 -0000 @@ -7,7 +7,7 @@ class BookTestCase extends DrupalWebTest function getInfo() { return array( 'name' => t('Book functionality'), - 'description' => t('Create a book, add pages, and test book interface.'), + 'description' => t('Create a book, add pages, test book interface, and then delete.'), 'group' => t('Book'), ); } @@ -23,6 +23,8 @@ class BookTestCase extends DrupalWebTest // Create users. $book_author = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books')); $web_user = $this->drupalCreateUser(array('access printer-friendly version')); + $book_outliner = $this->drupalCreateUser(array('administer book outlines')); + $admin_user = $this->drupalCreateUser(array('administer book outlines', 'bypass node access')); // Create new book. $this->drupalLogin($book_author); @@ -76,6 +78,16 @@ class BookTestCase extends DrupalWebTest $this->book = $other_book; $this->checkBookNode($other_book, array($node), false, false, $node); $this->checkBookNode($node, NULL, $other_book, $other_book, false); + + // Check that the book can be deleted. + $this->drupalLogout(); + $this->drupalLogin($book_outliner); + // The book outliner user should not be able to delete books. + $this->deleteBook($book, FALSE); + $this->drupalLogout(); + $this->drupalLogin($admin_user); + // The admin user should be able to delete books. + $this->deleteBook($book, TRUE); } /** @@ -171,6 +183,41 @@ class BookTestCase extends DrupalWebTest return $node; } + + /** + * Delete the entire book. + * + * @param integer $book_nid the book's node id. + * @param string $book_title the book's title. + */ + function deleteBook($book, $access) { + // Make sure users without access don't see the delete link or title and can't go there. + if (!$access) { + $this->drupalGet('admin/content/book/list'); + $this->assertNoRaw(l(t('delete book'), 'admin/content/book/delete/' . $book->nid), t('The delete link does not show up on the book list')); + $this->assertNoRaw(t('Delete'), t('The Delete header does not show on the page')); + $this->drupalGet('admin/content/book/delete/' . $book->nid); + $this->assertNoRaw(t('Are you sure you want to delete the entire book %title?', array('%title' => $book->title)), t('Confirm that user can\'t delete a book.')); + } + else { + // Make sure the book pages are in the database and the delete link shows up on the book list page. + $this->assertFalse(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book->nid)) == 0, t('Book pages are listed in database.')); + $this->drupalGet('admin/content/book/list'); + $this->assertRaw(l(t('delete book'), 'admin/content/book/delete/' . $book->nid), t('The delete link shows up on the book list')); + + // Delete the book + $edit = array(); + $this->drupalGet('admin/content/book/delete/' . $book->nid); + $this->assertRaw(t('Are you sure you want to delete the entire book %title?', array('%title' => $book->title)), t('Confirm that user wants to delete book.')); + $this->drupalPost(NULL, $edit, t('Delete')); + + // Confirm deletion. + $this->assertRaw(t('The book has been deleted.'), t('Message confirms deletion')); + $this->drupalGet('admin/content/book/list'); + $this->assertNoRaw(l(t('delete book'), 'admin/content/book/delete/'. $book->nid), t('The delete link does not show up on the book list')); + $this->assertTrue(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book->nid)) == 0, t('Book pages not in database.')); + } + } } class BookBlockTestCase extends DrupalWebTestCase {