Delete Entire Book

kleinmp - January 22, 2009 - 20:47
Project:Drupal
Version:8.x-dev
Component:book.module
Category:feature request
Priority:normal
Assigned:Unassigned
Status:needs work
Issue tags:API addition, API change
Description

It would be nice to have a way to delete an entire book at once, instead of page by page. I took the book_delete.module, and I added it into book.module. I also created tests for deletion and put them in book.test.

There is still a minor issue about it listed at this link: http://drupal.org/node/362701

AttachmentSize
book_delete.patch7.57 KB
Testbed results
book_delete.patchfailedFailed: Failed to apply patch. Detailed results

#1

System Message - January 22, 2009 - 21:05
Status:needs review» needs work

The last submitted patch failed testing.

#2

kleinmp - January 22, 2009 - 22:36
Status:needs work» needs review

Ok, I remade the patch from the drupal root folder and tested it.

AttachmentSize
book_delete.patch 7.69 KB
Testbed results
book_delete.patchfailedFailed: Failed to apply patch. Detailed results

#3

Jody Lynn - January 23, 2009 - 21:16
Status:needs review» needs work

Cool
In book_admin_overview, you should not show the 'delete' link if the user does not have access to it.

#4

dereine - January 25, 2009 - 13:55

there are also some style problems see

+      $rows[] = array(l($book['title'], $book['href'], $book['options']), l(t('edit order and titles'), "admin/content/book/". $book['nid']), l(t('delete book'), 'admin/content/book/delete/'. $book['nid']));
+    }
+    $headers = array(t('Book'), t('Operations'), t('Delete'));
Its the wrong intention and code style of d7 is to seperate 'foo' . $variable

If you add access checking for the delete link you have to change your test too

But anything else looks fine for me,

#5

mr.baileys - January 26, 2009 - 11:53

Small nitpicky comment:
There seems to be a verb missing in the following comment:

+ * Menu item access callback - determine if the user delete entire books.

Should probably read: ... determine if the user can delete ...

#6

kleinmp - January 26, 2009 - 18:32

Thanks everyone!
I made the suggested changes. Now, only users who are allowed to delete books will see the link on the book admin page, and I changed the test to check for this.

@dereine - I added separation for the concatenation, but I'm not entirely sure if that's what you meant.

'admin/content/book/delete/' . $book['nid'])

AttachmentSize
book_delete.patch 9.1 KB
Testbed results
book_delete.patchfailedFailed: Failed to apply patch. Detailed results

#7

kleinmp - January 26, 2009 - 18:33
Status:needs work» needs review

Forgot to change the status.

#8

Jody Lynn - January 26, 2009 - 22:34

Made couple changes:
Moved all the book deletion code into book.admin.inc (I believe this has a mild performance improvement to move admin stuff into these files, and at any rate keeps the files better organized)
Refactored book_admin_overview a little to prevent need for redundant code
Minor comment fixes in the tests related to capitalization and punctuation and added a few comments.
Fixed that D7 code style concatenation issue in a few places (using space before and after . )
Passed the entire $book object to test deleteBook function to prevent using somewhat redundant parameters.
Added some $this->drupalGet to the no-access test of deleteBook. Must first go to the designated pages before running assertNoRaws.

AttachmentSize
book_delete3.patch 9.2 KB
Testbed results
book_delete3.patchfailedFailed: 9380 passes, 3 fails, 0 exceptions Detailed results

#9

Jody Lynn - January 26, 2009 - 22:38

Same thing, but hopefully without any tabs in it...

AttachmentSize
book_delete3.patch 9.28 KB
Testbed results
book_delete3.patchfailedFailed: 9309 passes, 3 fails, 0 exceptions Detailed results

#10

System Message - January 26, 2009 - 22:55
Status:needs review» needs work

The last submitted patch failed testing.

#11

Jody Lynn - January 29, 2009 - 20:37
Status:needs work» needs review

Because I moved the batch functions into book.admin.inc I needed to define the file in the batch.

AttachmentSize
book_delete_4.patch 9.22 KB
Testbed results
book_delete_4.patchfailedFailed: Failed to install HEAD. a href=http://testing.drupal.org/pifr/file/1/book_delete_4.patchDetailed results/a

#12

System Message - January 29, 2009 - 20:50
Status:needs review» needs work

The last submitted patch failed testing.

#13

Jody Lynn - January 30, 2009 - 17:03
Status:needs work» needs review

Testing server failed.

AttachmentSize
book_delete_4.patch 9.22 KB
Testbed results
book_delete_4.patchfailedFailed: 9707 passes, 7 fails, 0 exceptions a href=http://testing.drupal.org/pifr/file/1/book_delete_4_0.patchDetailed results/a

#14

gpk - January 30, 2009 - 23:07

#15

System Message - February 3, 2009 - 21:50
Status:needs review» needs work

The last submitted patch failed testing.

#16

justinrandell - February 5, 2009 - 19:01

i had a look at this, and it looks good.

the only thing that struck me was this:

<?php
$this
->assertFalse(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book->nid)) == 0, t('Book pages are listed in database.'));
?>

i'm not sure that we need that. if we do, its perhaps better placed in the testBook method. that assertion is relevant to more than just the book delete case, so we should probably start complaining about it earlier than the book delete.

also, it could probably be a it more specific than a boolean, as we know exactly how big the count should be.

all tests pass for me, but leaving as CNW based on the comments above.

#17

kleinmp - February 5, 2009 - 20:01
Status:needs work» needs review

Thanks, Justin. I deleted that line. I guess it was just a vestigial test from when I was testing the tests.:)
Here's the updated patch.

AttachmentSize
book_delete_4.patch 9.03 KB
Testbed results
book_delete_4.patchfailedFailed: Failed to apply patch. Detailed results

#18

System Message - February 5, 2009 - 20:20
Status:needs review» needs work

The last submitted patch failed testing.

#19

justinrandell - February 6, 2009 - 02:57

updated patch to chase HEAD - this broke things: #369460: Add "No books available." message to admin book list.

AttachmentSize
book_delete.patch 9.29 KB
Testbed results
book_delete.patchfailedFailed: 9725 passes, 0 fails, 1 exception Detailed results

#20

justinrandell - February 6, 2009 - 02:57
Status:needs work» needs review

woops, update status too.

#21

justinrandell - February 6, 2009 - 09:21
Status:needs review» reviewed & tested by the community

test bot is happy, i think this is RTBC.

#22

System Message - February 7, 2009 - 03:40
Status:reviewed & tested by the community» needs work

The last submitted patch failed testing.

#23

pwolanin - February 8, 2009 - 18:10
Status:needs work» duplicate

Duplicate to: http://drupal.org/node/283045

#24

kleinmp - February 19, 2009 - 15:46
Status:duplicate» needs work

While it is related, this seems to be a different issue than http://drupal.org/node/283045. That one is talking about being able to remove just the top-level node of a book when it has no children. This issue is about deleting an entire book at on time, rather than doing it page by page.

#25

deviantintegral - April 29, 2009 - 00:11
Status:needs work» needs review

This is an different issue than #283045: Allow top-level pages to be removed from books.

I've rerolled this patch for head, and changed some of the test messages so that they are proper sentences like the other tests in book.test. IMO this is RTBC, but I'd like someone else to confirm that as well.

AttachmentSize
362795_delete_entire_book_25.patch 9.01 KB
Testbed results
362795_delete_entire_book_25.patchfailedFailed: Failed to apply patch. Detailed results

#26

Arancaytar - April 29, 2009 - 01:07
Status:needs review» needs work

@@ -190,6 +202,40 @@

     return $node;
   }

+  /**

+      $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

Empty lines must not contain trailing spaces. Also, the inline comment has to end in a period.

'admin/content/book/delete/'. $book->nid)

Operators need a space on each side now (we used to leave out the space next to quoted strings, like here, but not anymore).

-    $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']));

There is no reason to change the single-quote to a double quote here.

+ * Menu callback.  Ask for confirmation of book deletion.

There's one extra space after the period there.

There are more little issues later on. I suggest you run the changed code file through the coder script to fix the style errors.

#27

pwolanin - April 29, 2009 - 01:56

why 5?

+/**
+ * Batch processing callback.  Delete an entire book 5 nodes at a time.
+ */
+function book_delete($bid, &$context) {

#28

Jody Lynn - April 29, 2009 - 18:22

I did the cleanup pointed out by Arancaytar as well as using coder to find a few additional trailing spaces.

@pwolanin, I switched the batch processing to use a $limit = 5 to be consistent with other batch processing. Some limit does have to be chosen and for deletion it needs to be fairly low since hundreds of search index deletions can occur for each node deleted (that seems to be the slowest factor in most cases).

AttachmentSize
book-delete.patch 9.4 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#29

kleinmp - April 29, 2009 - 19:15
Status:needs work» needs review

#30

Berdir - April 29, 2009 - 20:12
Status:needs review» needs work

All patches should use DBTNG/convert things they change to DBTNG....

+  $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, $limit);

db_query_range is fine, but this needs to use the new DBTNG style placeholders, see http://drupal.org/node/310072

+    $context['sandbox']['max'] = db_result(db_query("SELECT COUNT(nid) FROM {book} WHERE bid = %d", $bid));
....

+      $this->assertTrue(db_result(db_query('SELECT COUNT(nid) FROM {book} WHERE bid = %d', $book->nid)) == 0, t('The book pages are not in the database.'));

Again, use named placeholders and db_query()->fetchField() instead of db_result()

#31

Jody Lynn - April 29, 2009 - 21:31
Status:needs work» needs review

Updated the queries to DBTNG

AttachmentSize
book-delete.patch 9.47 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#32

deviantintegral - May 16, 2009 - 20:21

Here is an updated patch which adds in some additional comments about why 5 nodes are deleted at a time, removes a couple of extra spaces in comments, and changes the title of the batch operation to 'Deleting book %title'. It also adds a blank line to the end of book.module so diff doesn't complain.

AttachmentSize
book-delete.patch 9.29 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#33

pwolanin - May 17, 2009 - 00:10
Status:needs review» needs work

Generally looks good - minor quibble though - you call user_access('bypass node access') repeatedly for each loop iteration. You should just call it once and save the value at the beginning of the admin overview function.

Also, please diff with -p

#34

pwolanin - May 17, 2009 - 00:32

Given that it's a rarely accessed admin page, probably the extra function call doesn't matter.

However, as the code is now, I can get to this page callback with a node that's not a book:

function book_delete_confirm(&$form_state, $node) {

A check that the node is the top-level node in a book should be part of the access callback. Ideally add a check for that in the test case also.

#35

deviantintegral - May 17, 2009 - 01:24
Status:needs work» needs review

Good catch. Here is an update with an associated test.

AttachmentSize
book-delete.patch 10.42 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#36

pwolanin - May 17, 2009 - 01:40
Status:needs review» needs work

No, I think this is not right:

+function _book_delete_access($node) {
+  return user_access('administer book outlines') && user_access('bypass node access') && isset($node->book);
+}

That only checks that the node is any part of any book.

I think you want:

return user_access('administer book outlines') && user_access('bypass node access') && isset($node->book['bid']) && ($node->book['bid'] == $node->nid);

Since by definition the bid is the nid of the top node.

#37

deviantintegral - May 18, 2009 - 16:25
Status:needs work» needs review

The attached:

  • Checks the node type and if it's a book root in a page callback so drupal_not_found() can be called instead of access denied. It would probably confusing to hit that page as UID 1 and get access denied.
  • Implements the change from #36.
  • Fixes a typo in a test name.
AttachmentSize
book-delete.patch 10.52 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#38

Jody Lynn - May 18, 2009 - 16:32
Status:needs review» needs work

<?php
// Users should only be able to delete books, not nodes of other types.
+    $page = $this->drupalCreateNode();
+   
$this->deleteBook($page, TRUE);
?>

Shouldn't this test actually be false?

#39

deviantintegral - May 18, 2009 - 17:13
Status:needs work» needs review

No, because the user does have access to delete books, but no one should be able to delete nodes which aren't books. If that parameter is FALSE, the page should still be a 404 as $page is not a valid book, so in this case changing the parameter should still have the same result.

#40

pwolanin - May 19, 2009 - 12:29
Status:needs review» needs work

I don't understand - why is there not a check in the access callback?

#41

deviantintegral - May 19, 2009 - 17:15
Status:needs work» needs review

If the access callback returns FALSE, then a 403 is returned. That implies that given the right permissions, a user should be able to view that URL. That isn't the case where a node doesn't exist or isn't a book. So, if the node is not eligible to be deleted through the book UI, it returns a 404. If the node is eligible, but you don't have proper permissions, it returns a 403. This is how node/%node/edit behaves, for example.

I had thought about doing the check in the access callback and calling drupal_not_found() from there, but then you have to call exit() after, which I imagine breaks things.

#42

pwolanin - May 19, 2009 - 19:42

oh, ok - I see the logic. Though you could also do that with a custom load function - that would be preferred in some ways.

If going with the current logic, I'd change:

return drupal_not_found();

to

return MENU_NOT_FOUND;

#43

pwolanin - May 19, 2009 - 19:47

e.g.:

$items['admin/content/book/delete/%book']

<?php
function book_load($nid) {
 
$node = node_load($nid);
  if (isset(
$node->nid) && isset($node->book) && ($node->nid == $node->book['bid'])) {
    return
$node;
  }
  return
FALSE;
}
?>

#44

deviantintegral - May 21, 2009 - 16:11

Updated with the menu loader function from #43, and removing the page callback. If book_load() gets committed, another issue should be opened for any hook_menu() entries which should be converted from %node to %book.

AttachmentSize
book-delete.patch 11.05 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#45

Berdir - May 22, 2009 - 10:42

+  $result = db_query_range("SELECT nid FROM {book} WHERE bid = :bid AND nid > :nid AND nid <> bid ORDER BY nid ASC", array(
+    ':bid' => $context['sandbox']['bid'],
+    ':nid' => $context['sandbox']['highest_nid'],
+    ), 0, $limit);
+  while ($nid = $result->fetchField()) {
+    node_delete($nid);
+    // Update our progress information.
+    $context['sandbox']['progress']++;
+    $context['sandbox']['highest_nid'] = $nid;
+  }

I am not sure, but it would maybe be shorter by using node_delete_multiple() and fetchCol(), something like (untested):
<?php
  $nids
= db_query_range("SELECT nid FROM {book} WHERE bid = :bid AND nid > :nid AND nid <> bid ORDER BY nid ASC", array(
   
':bid' => $context['sandbox']['bid'],
   
':nid' => $context['sandbox']['highest_nid'],
  ),
0, $limit)->fetchCol();
 
node_delete_multiple($nids);
 
$context['sandbox']['progress'] += count($nids);
 
$context['sandbox']['progress'] = array_pop($nids);
?>

Advantage: node_delete_multiple does then fetch all nodes in a single query.

#46

Berdir - May 22, 2009 - 10:43
Status:needs review» needs work

#47

Jody Lynn - July 5, 2009 - 19:00
Status:needs work» needs review

Cool, that's an improvement and works.

AttachmentSize
book-delete.patch 11.13 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#48

System Message - July 14, 2009 - 21:05
Status:needs review» needs work

The last submitted patch failed testing.

#49

Jody Lynn - August 10, 2009 - 03:46
Status:needs work» needs review

I want a retest

#50

Jody Lynn - August 10, 2009 - 21:40
AttachmentSize
book-delete.patch 11.13 KB
Testbed results
book-delete.patchfailedFailed: Failed to apply patch. Detailed results

#51

System Message - August 10, 2009 - 21:47
Status:needs review» needs work

The last submitted patch failed testing.

#52

Jody Lynn - August 11, 2009 - 22:17
Status:needs work» needs review

Ok, rerolled

AttachmentSize
book-delete.patch 11.18 KB
Testbed results
book-delete.patchfailedFailed: Invalid PHP syntax in modules/book/book.module. Detailed results

#53

Dr Trichome - August 23, 2009 - 16:48

Will there be a patch for Drupal 6?

#54

deviantintegral - August 23, 2009 - 18:05

@Dr Trichome, no, since this is a feature addition. Use the Book Delete module instead - it works quite nicely.

#55

Dr Trichome - August 23, 2009 - 18:20

Thanks! Will do.

#56

System Message - August 24, 2009 - 16:06
Status:needs review» needs work

The last submitted patch failed testing.

#57

Jody Lynn - August 29, 2009 - 18:24
Status:needs work» needs review

Reroll

AttachmentSize
book-delete_8.patch 11.52 KB
Testbed results
book-delete_8.patchfailedFailed: 13014 passes, 4 fails, 7 exceptions Detailed results

#58

pwolanin - August 29, 2009 - 20:02

new function book_load($nid) seems not to be used anywhere in the patch. Why is that added?

#59

Jody Lynn - August 29, 2009 - 21:01

LOL, that was your suggestion actually in comment #43, it's a menu load function.

#60

pwolanin - August 29, 2009 - 21:11

ah, there you go - that's what I get for not re-reading the thread. I see now

-  $items['admin/content/book/%node'] = array(
+  $items['admin/content/book/%book'] = array(

etc

I would need to test, but looks rtbc.

#61

kleinmp - August 31, 2009 - 14:40
Status:needs review» reviewed & tested by the community

The code looks good and the patch is working nicely for me.

#62

pwolanin - August 31, 2009 - 16:28

tagging

#63

System Message - September 18, 2009 - 09:00
Status:reviewed & tested by the community» needs work

The last submitted patch failed testing.

#64

deviantintegral - September 23, 2009 - 17:06
Status:needs work» needs review

Here is an updated patch which:

  • Fixes the parameters for the book_delete_confirm() callback.
  • Fixes the arguments for the db_query_range() call in the batch callback.
  • Checks the return value of the db_query_range() call in the case that the book contains exactly one node. Perhaps we also want a test for this?
AttachmentSize
book-delete_9.patch 11.53 KB
Testbed results
book-delete_9.patchfailedFailed: 13753 passes, 0 fails, 14 exceptions Detailed results

#65

deviantintegral - October 11, 2009 - 16:36
Status:needs review» reviewed & tested by the community

As what's in #64 fixes API changes for the most part, I'm setting this to RTBC.

#66

Dries - October 11, 2009 - 18:57

This looks like a new feature not in the D8 exception list, to be honest.

#67

System Message - October 11, 2009 - 22:35
Status:reviewed & tested by the community» needs work

The last submitted patch failed testing.

#68

Jody Lynn - October 12, 2009 - 15:27
Version:7.x-dev» 8.x-dev

I'll make a D7 branch for book_delete contrib module and push this feature to d8.

 
 

Drupal is a registered trademark of Dries Buytaert.