Problem/Motivation

When attempting to delete a book parent with children through the UI:
Recoverable fatal error: Argument 1 passed to Drupal\book\BookManager::updateOutline() must implement interface Drupal\node\NodeInterface, array given, called in /path/core/modules/book/src/BookManager.php on line 442 and defined in Drupal\book\BookManager->updateOutline() (line 250 of core/modules/book/src/BookManager.php).

on second attempt, this error isn't thrown, but then when viewing a child page:

Fatal error: Call to a member function access() on a non-object in /path/core/modules/book/book.module on line 239

To reproduce:

  1. Enable the book module
  2. Add a top-level book node
  3. Add one or more child book nodes
  4. Attempt to delete the parent node, note the exception. Try again and it succeeds.
  5. Visit one of the child nodes and note the fatal error.

Proposed resolution

  1. Addressing the first issue resolves the second

Remaining tasks

User interface changes

API changes

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because fatal errors and exceptions
Issue priority Major because removing a parent book node leaves the child nodes inaccessible. Logic is in place to ensure this doesn't happen, but it got left behind due to a lack of tests.
CommentFileSizeAuthor
#51 Child node created before applying patch was deleted without erroe in first attempt.PNG61.55 KBswati_qa
#51 Parent Node was deleted in first attempt without error.PNG56.27 KBswati_qa
#51 Patch applied.PNG43.93 KBswati_qa
#51 Retest patches created.PNG55.69 KBswati_qa
#51 Created Nodes before applying patch.PNG69.95 KBswati_qa
#50 2482857-50.patch4.89 KBjhedstrom
#50 interdiff.txt1.37 KBjhedstrom
#47 2482857-47.patch5.68 KBjhedstrom
#47 2482857-47-TEST-ONLY.patch3.28 KBjhedstrom
#47 interdiff.txt3.69 KBjhedstrom
#43 2482857-43.patch5.47 KBlindzeng
#40 After_patch_childpage_accesible.JPG107.42 KBTruptti
#40 After_patch_book_deleted.JPG106.14 KBTruptti
#40 Patch_applied.JPG89.9 KBTruptti
#40 B4_patch_fatal_error_on_accessing_child_page.JPG360.41 KBTruptti
#28 2482857-27.patch5.47 KBlokapujya
#28 2482857-28-test-only.patch2.7 KBlokapujya
#27 2482857-27.patch5.47 KBlokapujya
#19 increment.txt1.98 KBpwolanin
#19 book-delete-248285-19.patch5.44 KBpwolanin
#12 interdiff.txt941 bytesjhedstrom
#12 book-delete-2482857-12.patch4.95 KBjhedstrom
#10 book-delete-2482857-10.patch4.96 KBjhedstrom
#10 interdiff.txt2.53 KBjhedstrom
#4 book-delete-2482857-04.patch4.89 KBjhedstrom
#4 book-delete-2482857-04-TEST-ONLY.patch2.49 KBjhedstrom
#4 interdiff.txt3.24 KBjhedstrom
#1 book-delete-2482857-01.patch3.17 KBjhedstrom
#1 book-delete-2482857-01-TEST-ONLY.patch2.5 KBjhedstrom
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jhedstrom’s picture

Status: Active » Needs review
FileSize
2.5 KB
3.17 KB

Not a complete fix yet, but here's a test that illustrates the issue, and the beginnings of a fix.

Status: Needs review » Needs work

The last submitted patch, 1: book-delete-2482857-01.patch, failed testing.

The last submitted patch, 1: book-delete-2482857-01-TEST-ONLY.patch, failed testing.

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
3.24 KB
2.49 KB
4.89 KB

This gets things back to working as expected.

jhedstrom’s picture

Issue summary: View changes

The last submitted patch, 4: book-delete-2482857-04-TEST-ONLY.patch, failed testing.

jhedstrom’s picture

Issue summary: View changes

Adding a beta phase evaluation.

jhedstrom’s picture

Assigned: jhedstrom » Unassigned
pwolanin’s picture

Status: Needs review » Needs work

minor, please use route names instead of paths when adding test code:

+     $this->drupalGet('node/' . $this->book->id() . '/delete');

I think you might be able also to use the base for ID like: book_form_node_form_alter ?

So at least you won't run this code in every form

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
2.53 KB
4.96 KB

I didn't know about the ability to pass a route to drupalGet(), that makes much more sense. Also changing to the base form works, and greatly simplifies the function.

pwolanin’s picture

Status: Needs review » Needs work

These 2 lines should also use the routes via a Url object

+     $this->drupalPostForm('node/' . $this->book->id() . '/delete', [], t('Delete'));
+     $this->drupalGet('node/' . $nodes[0]->id());

like:

    $url = Url::fromRoute('entity.node.delete_form', ['node' =>$this->book->id()])
    $this->drupalPostForm($url, [], t('Delete'));
   $url = Url::fromRoute('entity.node.canonical', ['node' =>$nodes[0]->id()])
    $this->drupalGet($url);
jhedstrom’s picture

Status: Needs work » Needs review
FileSize
4.95 KB
941 bytes

Here's a patch that fixes the overlooked drupalGet calls mentioned in #11.

pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

Changes look good, assuming the tests are still green.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/book/book.module
@@ -335,18 +335,22 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
-function book_form_node_delete_confirm_alter(&$form, FormStateInterface $form_state) {
-  $node = Node::load($form['nid']['#value']);
+function book_form_node_confirm_form_alter(&$form, FormStateInterface $form_state) {

This loss of specificity in the form alter is worrying. I think we need to include the operation in the base form id.

pwolanin’s picture

@alexpottt - hmm, good point. But I don't think there is an option to include the operation? The form ID includes the bundle name which is too specific.

I had assumed that was delete specific, but I see it's more generic:

abstract class EntityConfirmFormBase extends EntityForm implements ConfirmFormInterface {

  /**
   * {@inheritdoc}
   */
  public function getBaseFormId() {
    return $this->entity->getEntityTypeId() . '_confirm_form';
  }

I'm not seeing an obvious way to get the operation other than looking at the form ID?

alexpott’s picture

+++ b/core/modules/book/book.module
@@ -335,18 +335,22 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
-      '#markup' => '<p>' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())) . '</p>',
+      '#markup' => '<p class="messages messages--warning">' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())) . '</p>',

Let's add the p tags with #prefix and #suffix.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
5.44 KB
1.98 KB

Per discussion with alexpott, fixing the base for ID to include the operation.

Status: Needs review » Needs work

The last submitted patch, 19: book-delete-248285-19.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review

test failed on settings.php, so I think it was a bot issue

jhedstrom’s picture

I think the patch in #19 has addressed Alex's feedback.

+++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
@@ -20,7 +20,7 @@
+    return $this->entity->getEntityTypeId() . '_' . $this->getOperation() . '_confirm';

This seems like a beneficial change. Does it need a CR?

Status: Needs review » Needs work

The last submitted patch, 19: book-delete-248285-19.patch, failed testing.

lokapujya’s picture

Status: Needs work » Needs review
FileSize
5.47 KB

Simple reroll.

lokapujya’s picture

Just want to see the test-only again.

The last submitted patch, 28: 2482857-28-test-only.patch, failed testing.

jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community

I think this is good now. If we need a CR, I can add one as mentioned in #24.

The last submitted patch, 28: 2482857-28-test-only.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 28: 2482857-27.patch, failed testing.

Status: Needs work » Needs review

lokapujya queued 28: 2482857-27.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 28: 2482857-27.patch, failed testing.

Status: Needs work » Needs review

lokapujya queued 28: 2482857-27.patch for re-testing.

The last submitted patch, 28: 2482857-28-test-only.patch, failed testing.

lokapujya’s picture

So if the book is deleted the direct child pages become a book (rather than parent of "none") which is the same as D7.

Note that node/{nid}/outline/remove is still not allowed (Access Denied) and has a test.

I don't see any reason for a CR.

RTBC from my testing.

mgifford queued 28: 2482857-27.patch for re-testing.

Truptti’s picture

Verified the patch "2482857-27.patch" mentioned in #27, the child nodes are now accessible after applying the patch
Steps performed to verify the patch:
1.Created a Book page "Test Page"
2.Created child nodes "Test page 1" and Test page 2".
3.Deleted the "Test Page" , while deleting got an error "The website encountered an unexpected error. Please try again later." on a second attempt it got deleted
4.Accessed "Test page 1" and "Test page 2" and got error "( ! ) Fatal error: Call to a member function access() on a non-object in C:\wamp\www\drupal\core\modules\book\book.module on line 233"
5.Applied the patch and repeated steps 1 to 4 with book names as "New Page" and child nodes as "Child page 1" and "Child page 2"
6."New Page" got deleted without any exception error and I am able to access "Child page 1" and "Child page2" without any fatal error.
7.However still the fatal error is displayed for "Test page 1" and "Test page 2" (the child pages created before applying the patch)

So if the point no 7 is meant to be like that then the issue can be marked as RTBC from the testing.

jhedstrom’s picture

Issue tags: +rc target triage
pwolanin’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

doesn't apply

lindzeng’s picture

Patch was applied with fuzz.

lindzeng’s picture

Status: Needs work » Needs review
jhedstrom’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs reroll

Back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -rc target triage
  1. +++ b/core/lib/Drupal/Core/Entity/ContentEntityConfirmFormBase.php
    @@ -20,7 +20,7 @@
    -    return $this->entity->getEntityTypeId() . '_confirm_form';
    +    return $this->entity->getEntityTypeId() . '_' . $this->getOperation() . '_confirm';
    

    This is an API change. I don't think we can do the fix this way now. I think we'll need to just change the book_form_node_delete_confirm_alter() to book_form_node_confirm_alter() and test the operation.

  2. +++ b/core/modules/book/book.module
    @@ -323,18 +323,24 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
    +  $types = \Drupal::config('book.settings')->get('allowed_types');
    ...
    +  if (!in_array($node->getType(), $types)) {
    +    // Not a book node.
    +    return;
    +  }
    

    Instead use book_type_is_allowed()

  3. +++ b/core/modules/book/book.module
    @@ -323,18 +323,24 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
    -      '#markup' => '<p>' . t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())) . '</p>',
    +      '#prefix' => '<p class="messages messages--warning">',
    +      '#markup' => t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', array('%title' => $node->label())),
    +      '#suffix' => '</p>',
    

    Why is this change necessary to fix the bug?

  4. +++ b/core/modules/book/src/BookManager.php
    @@ -439,9 +439,9 @@ public function deleteFromBook($nid) {
    -      foreach ($result as $child) {
    -        $child['bid'] = $child['nid'];
    +      $children = $this->entityManager->getStorage('node')->loadMultiple(array_keys($result));
    +      foreach ($children as $child) {
    +        $child->book['bid'] = $child->id();
             $this->updateOutline($child);
           }
    

    Is this change of the child bid explicitly tested?

  5. +++ b/core/modules/book/src/Tests/BookTest.php
    @@ -30,7 +31,7 @@ class BookTest extends WebTestBase {
    -   * @var object
    +   * @var \Drupal\node\NodeInterface
    
    @@ -77,11 +78,13 @@ protected function setUp() {
    -    $this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
    +    $this->adminUser = $this->drupalCreateUser(['create new books', 'create book content', 'edit any book content', 'delete any book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration']);
    ...
    +   *
    +   * @return \Drupal\node\NodeInterface[]
    

    Not in scope.

jhedstrom’s picture

Status: Needs work » Needs review
FileSize
3.69 KB
3.28 KB
5.68 KB

This patch addresses #46:

  1. Done
  2. Done
  3. Not necessary, but with the warning it stands out much more, and adding the class wrapper isn't a string change. Can be removed if this is too much for a bug fix.
  4. Added a test to explicitly check this
  5. Removed the change to array brackets, but left the doc block enhancement. It is a nice DX fix, but could be removed if that is out of scope too.
alexpott’s picture

Status: Needs review » Needs work

Re #47.3 I don't think we should be making UI changes in a bugfix issue. It was this way in Drupal 7 when book deletion worked. I'm not going to push on #47.5 other than to say that on bigger issues it is these type of out of scope changes that makes large patches hard to review because you have to go back and think so why is that change relevant to the scope of this issue.

+++ b/core/modules/book/src/Tests/BookTest.php
@@ -10,6 +10,8 @@
+use Drupal\node\Entity\Node;

Is this used?

The last submitted patch, 47: 2482857-47-TEST-ONLY.patch, failed testing.

jhedstrom’s picture

This removes the warning class from the message.

I'd happily do DX docs follow-ups, but test-only patches are very hard to get reviewed (historically anyway).

swati_qa’s picture

Verified the patch "2482857-50.patch" mentioned in #50.
Steps performed to verify the patch:
1. Created a Book page "Parent Node"
2. Created child nodes "Child Node 1" and "Child Node 2".
3. Deleted the "Parent Node" , while deleting got an error "The website encountered an unexpected error. Please try again later." on a second attempt it got deleted
4. Accessed "Child Node 1" and "Child Node 2" and didn't get any error.
5. Applied the patch and repeated steps 1 to 4 with book names as "Patch_retest_Parent Node" and child nodes as "Patch retest_Child Node" and "Patch retest_Child Node 2"
6. "Patch_retest_Parent Node" got deleted without any exception error and I am able to access "Patch retest_Child Node" and "Patch retest_Child Node 2" without any fatal error.
7. No error is displayed for "Child Node 1" (the child page created before applying the patch).

So, issue can be marked as "PASSED" for errors and warnings.

swati_qa’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed a224bb8 and pushed to 8.0.x and 8.1.x. Thanks!

  • alexpott committed d04f659 on 8.1.x
    Issue #2482857 by jhedstrom, lokapujya, pwolanin, lindzeng, swati_qa,...

  • alexpott committed a224bb8 on
    Issue #2482857 by jhedstrom, lokapujya, pwolanin, lindzeng, swati_qa,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.