66edec91e0df53cf795e811ec1fd62132d2a7a9a
[yaffs-website] / web / core / modules / book / tests / src / Functional / BookTest.php
1 <?php
2
3 namespace Drupal\Tests\book\Functional;
4
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Entity\EntityInterface;
8 use Drupal\Tests\BrowserTestBase;
9 use Drupal\user\RoleInterface;
10
11 /**
12  * Create a book, add pages, and test book interface.
13  *
14  * @group book
15  */
16 class BookTest extends BrowserTestBase {
17
18   /**
19    * Modules to install.
20    *
21    * @var array
22    */
23   public static $modules = ['book', 'block', 'node_access_test', 'book_test'];
24
25   /**
26    * A book node.
27    *
28    * @var \Drupal\node\NodeInterface
29    */
30   protected $book;
31
32   /**
33    * A user with permission to create and edit books.
34    *
35    * @var object
36    */
37   protected $bookAuthor;
38
39   /**
40    * A user with permission to view a book and access printer-friendly version.
41    *
42    * @var object
43    */
44   protected $webUser;
45
46   /**
47    * A user with permission to create and edit books and to administer blocks.
48    *
49    * @var object
50    */
51   protected $adminUser;
52
53   /**
54    * A user without the 'node test view' permission.
55    *
56    * @var \Drupal\user\UserInterface
57    */
58   protected $webUserWithoutNodeAccess;
59
60   /**
61    * {@inheritdoc}
62    */
63   protected function setUp() {
64     parent::setUp();
65     $this->drupalPlaceBlock('system_breadcrumb_block');
66     $this->drupalPlaceBlock('page_title_block');
67
68     // node_access_test requires a node_access_rebuild().
69     node_access_rebuild();
70
71     // Create users.
72     $this->bookAuthor = $this->drupalCreateUser(['create new books', 'create book content', 'edit own book content', 'add content to books']);
73     $this->webUser = $this->drupalCreateUser(['access printer-friendly version', 'node test view']);
74     $this->webUserWithoutNodeAccess = $this->drupalCreateUser(['access printer-friendly version']);
75     $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']);
76   }
77
78   /**
79    * Creates a new book with a page hierarchy.
80    *
81    * @return \Drupal\node\NodeInterface[]
82    */
83   public function createBook() {
84     // Create new book.
85     $this->drupalLogin($this->bookAuthor);
86
87     $this->book = $this->createBookNode('new');
88     $book = $this->book;
89
90     /*
91      * Add page hierarchy to book.
92      * Book
93      *  |- Node 0
94      *   |- Node 1
95      *   |- Node 2
96      *  |- Node 3
97      *  |- Node 4
98      */
99     $nodes = [];
100     $nodes[] = $this->createBookNode($book->id()); // Node 0.
101     $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 1.
102     $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 2.
103     $nodes[] = $this->createBookNode($book->id()); // Node 3.
104     $nodes[] = $this->createBookNode($book->id()); // Node 4.
105
106     $this->drupalLogout();
107
108     return $nodes;
109   }
110
111   /**
112    * Tests the book navigation cache context.
113    *
114    * @see \Drupal\book\Cache\BookNavigationCacheContext
115    */
116   public function testBookNavigationCacheContext() {
117     // Create a page node.
118     $this->drupalCreateContentType(['type' => 'page']);
119     $page = $this->drupalCreateNode();
120
121     // Create a book, consisting of book nodes.
122     $book_nodes = $this->createBook();
123
124     // Enable the debug output.
125     \Drupal::state()->set('book_test.debug_book_navigation_cache_context', TRUE);
126     Cache::invalidateTags(['book_test.debug_book_navigation_cache_context']);
127
128     $this->drupalLogin($this->bookAuthor);
129
130     // On non-node route.
131     $this->drupalGet($this->adminUser->urlInfo());
132     $this->assertRaw('[route.book_navigation]=book.none');
133
134     // On non-book node route.
135     $this->drupalGet($page->urlInfo());
136     $this->assertRaw('[route.book_navigation]=book.none');
137
138     // On book node route.
139     $this->drupalGet($book_nodes[0]->urlInfo());
140     $this->assertRaw('[route.book_navigation]=0|2|3');
141     $this->drupalGet($book_nodes[1]->urlInfo());
142     $this->assertRaw('[route.book_navigation]=0|2|3|4');
143     $this->drupalGet($book_nodes[2]->urlInfo());
144     $this->assertRaw('[route.book_navigation]=0|2|3|5');
145     $this->drupalGet($book_nodes[3]->urlInfo());
146     $this->assertRaw('[route.book_navigation]=0|2|6');
147     $this->drupalGet($book_nodes[4]->urlInfo());
148     $this->assertRaw('[route.book_navigation]=0|2|7');
149   }
150
151   /**
152    * Tests saving the book outline on an empty book.
153    */
154   public function testEmptyBook() {
155     // Create a new empty book.
156     $this->drupalLogin($this->bookAuthor);
157     $book = $this->createBookNode('new');
158     $this->drupalLogout();
159
160     // Log in as a user with access to the book outline and save the form.
161     $this->drupalLogin($this->adminUser);
162     $this->drupalPostForm('admin/structure/book/' . $book->id(), [], t('Save book pages'));
163     $this->assertText(t('Updated book @book.', ['@book' => $book->label()]));
164   }
165
166   /**
167    * Tests book functionality through node interfaces.
168    */
169   public function testBook() {
170     // Create new book.
171     $nodes = $this->createBook();
172     $book = $this->book;
173
174     $this->drupalLogin($this->webUser);
175
176     // Check that book pages display along with the correct outlines and
177     // previous/next links.
178     $this->checkBookNode($book, [$nodes[0], $nodes[3], $nodes[4]], FALSE, FALSE, $nodes[0], []);
179     $this->checkBookNode($nodes[0], [$nodes[1], $nodes[2]], $book, $book, $nodes[1], [$book]);
180     $this->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2], [$book, $nodes[0]]);
181     $this->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3], [$book, $nodes[0]]);
182     $this->checkBookNode($nodes[3], NULL, $nodes[2], $book, $nodes[4], [$book]);
183     $this->checkBookNode($nodes[4], NULL, $nodes[3], $book, FALSE, [$book]);
184
185     $this->drupalLogout();
186     $this->drupalLogin($this->bookAuthor);
187
188     // Check the presence of expected cache tags.
189     $this->drupalGet('node/add/book');
190     $this->assertCacheTag('config:book.settings');
191
192     /*
193      * Add Node 5 under Node 3.
194      * Book
195      *  |- Node 0
196      *   |- Node 1
197      *   |- Node 2
198      *  |- Node 3
199      *   |- Node 5
200      *  |- Node 4
201      */
202     $nodes[] = $this->createBookNode($book->id(), $nodes[3]->book['nid']); // Node 5.
203     $this->drupalLogout();
204     $this->drupalLogin($this->webUser);
205     // Verify the new outline - make sure we don't get stale cached data.
206     $this->checkBookNode($nodes[3], [$nodes[5]], $nodes[2], $book, $nodes[5], [$book]);
207     $this->checkBookNode($nodes[4], NULL, $nodes[5], $book, FALSE, [$book]);
208     $this->drupalLogout();
209     // Create a second book, and move an existing book page into it.
210     $this->drupalLogin($this->bookAuthor);
211     $other_book = $this->createBookNode('new');
212     $node = $this->createBookNode($book->id());
213     $edit = ['book[bid]' => $other_book->id()];
214     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
215
216     $this->drupalLogout();
217     $this->drupalLogin($this->webUser);
218
219     // Check that the nodes in the second book are displayed correctly.
220     // First we must set $this->book to the second book, so that the
221     // correct regex will be generated for testing the outline.
222     $this->book = $other_book;
223     $this->checkBookNode($other_book, [$node], FALSE, FALSE, $node, []);
224     $this->checkBookNode($node, NULL, $other_book, $other_book, FALSE, [$other_book]);
225
226     // Test that we can save a book programatically.
227     $this->drupalLogin($this->bookAuthor);
228     $book = $this->createBookNode('new');
229     $book->save();
230   }
231
232   /**
233    * Checks the outline of sub-pages; previous, up, and next.
234    *
235    * Also checks the printer friendly version of the outline.
236    *
237    * @param \Drupal\Core\Entity\EntityInterface $node
238    *   Node to check.
239    * @param $nodes
240    *   Nodes that should be in outline.
241    * @param $previous
242    *   (optional) Previous link node. Defaults to FALSE.
243    * @param $up
244    *   (optional) Up link node. Defaults to FALSE.
245    * @param $next
246    *   (optional) Next link node. Defaults to FALSE.
247    * @param array $breadcrumb
248    *   The nodes that should be displayed in the breadcrumb.
249    */
250   public function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) {
251     // $number does not use drupal_static as it should not be reset
252     // since it uniquely identifies each call to checkBookNode().
253     static $number = 0;
254     $this->drupalGet('node/' . $node->id());
255
256     // Check outline structure.
257     if ($nodes !== NULL) {
258       $this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node @number outline confirmed.', ['@number' => $number]));
259     }
260     else {
261       $this->pass(format_string('Node %number does not have outline.', ['%number' => $number]));
262     }
263
264     // Check previous, up, and next links.
265     if ($previous) {
266       /** @var \Drupal\Core\Url $url */
267       $url = $previous->urlInfo();
268       $url->setOptions(['attributes' => ['rel' => ['prev'], 'title' => t('Go to previous page')]]);
269       $text = SafeMarkup::format('<b>‹</b> @label', ['@label' => $previous->label()]);
270       $this->assertRaw(\Drupal::l($text, $url), 'Previous page link found.');
271     }
272
273     if ($up) {
274       /** @var \Drupal\Core\Url $url */
275       $url = $up->urlInfo();
276       $url->setOptions(['attributes' => ['title' => t('Go to parent page')]]);
277       $this->assertRaw(\Drupal::l('Up', $url), 'Up page link found.');
278     }
279
280     if ($next) {
281       /** @var \Drupal\Core\Url $url */
282       $url = $next->urlInfo();
283       $url->setOptions(['attributes' => ['rel' => ['next'], 'title' => t('Go to next page')]]);
284       $text = SafeMarkup::format('@label <b>›</b>', ['@label' => $next->label()]);
285       $this->assertRaw(\Drupal::l($text, $url), 'Next page link found.');
286     }
287
288     // Compute the expected breadcrumb.
289     $expected_breadcrumb = [];
290     $expected_breadcrumb[] = \Drupal::url('<front>');
291     foreach ($breadcrumb as $a_node) {
292       $expected_breadcrumb[] = $a_node->url();
293     }
294
295     // Fetch links in the current breadcrumb.
296     $links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
297     $got_breadcrumb = [];
298     foreach ($links as $link) {
299       $got_breadcrumb[] = $link->getAttribute('href');
300     }
301
302     // Compare expected and got breadcrumbs.
303     $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.');
304
305     // Check printer friendly version.
306     $this->drupalGet('book/export/html/' . $node->id());
307     $this->assertText($node->label(), 'Printer friendly title found.');
308     $this->assertRaw($node->body->processed, 'Printer friendly body found.');
309
310     $number++;
311   }
312
313   /**
314    * Creates a regular expression to check for the sub-nodes in the outline.
315    *
316    * @param array $nodes
317    *   An array of nodes to check in outline.
318    *
319    * @return string
320    *   A regular expression that locates sub-nodes of the outline.
321    */
322   public function generateOutlinePattern($nodes) {
323     $outline = '';
324     foreach ($nodes as $node) {
325       $outline .= '(node\/' . $node->id() . ')(.*?)(' . $node->label() . ')(.*?)';
326     }
327
328     return '/<nav id="book-navigation-' . $this->book->id() . '"(.*?)<ul(.*?)' . $outline . '<\/ul>/s';
329   }
330
331   /**
332    * Creates a book node.
333    *
334    * @param int|string $book_nid
335    *   A book node ID or set to 'new' to create a new book.
336    * @param int|null $parent
337    *   (optional) Parent book reference ID. Defaults to NULL.
338    *
339    * @return \Drupal\node\NodeInterface
340    *   The created node.
341    */
342   public function createBookNode($book_nid, $parent = NULL) {
343     // $number does not use drupal_static as it should not be reset
344     // since it uniquely identifies each call to createBookNode().
345     static $number = 0; // Used to ensure that when sorted nodes stay in same order.
346
347     $edit = [];
348     $edit['title[0][value]'] = str_pad($number, 2, '0', STR_PAD_LEFT) . ' - SimpleTest test node ' . $this->randomMachineName(10);
349     $edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomMachineName(32) . ' ' . $this->randomMachineName(32);
350     $edit['book[bid]'] = $book_nid;
351
352     if ($parent !== NULL) {
353       $this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
354
355       $edit['book[pid]'] = $parent;
356       $this->drupalPostForm(NULL, $edit, t('Save'));
357       // Make sure the parent was flagged as having children.
358       $parent_node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($parent);
359       $this->assertFalse(empty($parent_node->book['has_children']), 'Parent node is marked as having children');
360     }
361     else {
362       $this->drupalPostForm('node/add/book', $edit, t('Save'));
363     }
364
365     // Check to make sure the book node was created.
366     $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
367     $this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
368     $number++;
369
370     return $node;
371   }
372
373   /**
374    * Tests book export ("printer-friendly version") functionality.
375    */
376   public function testBookExport() {
377     // Create a book.
378     $nodes = $this->createBook();
379
380     // Log in as web user and view printer-friendly version.
381     $this->drupalLogin($this->webUser);
382     $this->drupalGet('node/' . $this->book->id());
383     $this->clickLink(t('Printer-friendly version'));
384
385     // Make sure each part of the book is there.
386     foreach ($nodes as $node) {
387       $this->assertText($node->label(), 'Node title found in printer friendly version.');
388       $this->assertRaw($node->body->processed, 'Node body found in printer friendly version.');
389     }
390
391     // Make sure we can't export an unsupported format.
392     $this->drupalGet('book/export/foobar/' . $this->book->id());
393     $this->assertResponse('404', 'Unsupported export format returned "not found".');
394
395     // Make sure we get a 404 on a not existing book node.
396     $this->drupalGet('book/export/html/123');
397     $this->assertResponse('404', 'Not existing book node returned "not found".');
398
399     // Make sure an anonymous user cannot view printer-friendly version.
400     $this->drupalLogout();
401
402     // Load the book and verify there is no printer-friendly version link.
403     $this->drupalGet('node/' . $this->book->id());
404     $this->assertNoLink(t('Printer-friendly version'), 'Anonymous user is not shown link to printer-friendly version.');
405
406     // Try getting the URL directly, and verify it fails.
407     $this->drupalGet('book/export/html/' . $this->book->id());
408     $this->assertResponse('403', 'Anonymous user properly forbidden.');
409
410     // Now grant anonymous users permission to view the printer-friendly
411     // version and verify that node access restrictions still prevent them from
412     // seeing it.
413     user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['access printer-friendly version']);
414     $this->drupalGet('book/export/html/' . $this->book->id());
415     $this->assertResponse('403', 'Anonymous user properly forbidden from seeing the printer-friendly version when denied by node access.');
416   }
417
418   /**
419    * Tests the functionality of the book navigation block.
420    */
421   public function testBookNavigationBlock() {
422     $this->drupalLogin($this->adminUser);
423
424     // Enable the block.
425     $block = $this->drupalPlaceBlock('book_navigation');
426
427     // Give anonymous users the permission 'node test view'.
428     $edit = [];
429     $edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
430     $this->drupalPostForm('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID, $edit, t('Save permissions'));
431     $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users.");
432
433     // Test correct display of the block.
434     $nodes = $this->createBook();
435     $this->drupalGet('<front>');
436     $this->assertText($block->label(), 'Book navigation block is displayed.');
437     $this->assertText($this->book->label(), format_string('Link to book root (@title) is displayed.', ['@title' => $nodes[0]->label()]));
438     $this->assertNoText($nodes[0]->label(), 'No links to individual book pages are displayed.');
439   }
440
441   /**
442    * Tests BookManager::getTableOfContents().
443    */
444   public function testGetTableOfContents() {
445     // Create new book.
446     $nodes = $this->createBook();
447     $book = $this->book;
448
449     $this->drupalLogin($this->bookAuthor);
450
451     /*
452      * Add Node 5 under Node 2.
453      * Add Node 6, 7, 8, 9, 10, 11 under Node 3.
454      * Book
455      *  |- Node 0
456      *   |- Node 1
457      *   |- Node 2
458      *    |- Node 5
459      *  |- Node 3
460      *   |- Node 6
461      *    |- Node 7
462      *     |- Node 8
463      *      |- Node 9
464      *       |- Node 10
465      *        |- Node 11
466      *  |- Node 4
467      */
468     foreach ([5 => 2, 6 => 3, 7 => 6, 8 => 7, 9 => 8, 10 => 9, 11 => 10] as $child => $parent) {
469       $nodes[$child] = $this->createBookNode($book->id(), $nodes[$parent]->id());
470     }
471     $this->drupalGet($nodes[0]->toUrl('edit-form'));
472     // Snice Node 0 has children 2 levels deep, nodes 10 and 11 should not
473     // appear in the selector.
474     $this->assertNoOption('edit-book-pid', $nodes[10]->id());
475     $this->assertNoOption('edit-book-pid', $nodes[11]->id());
476     // Node 9 should be available as an option.
477     $this->assertOption('edit-book-pid', $nodes[9]->id());
478
479     // Get a shallow set of options.
480     /** @var \Drupal\book\BookManagerInterface $manager */
481     $manager = $this->container->get('book.manager');
482     $options = $manager->getTableOfContents($book->id(), 3);
483     $expected_nids = [$book->id(), $nodes[0]->id(), $nodes[1]->id(), $nodes[2]->id(), $nodes[3]->id(), $nodes[6]->id(), $nodes[4]->id()];
484     $this->assertEqual(count($options), count($expected_nids));
485     $diff = array_diff($expected_nids, array_keys($options));
486     $this->assertTrue(empty($diff), 'Found all expected option keys');
487     // Exclude Node 3.
488     $options = $manager->getTableOfContents($book->id(), 3, [$nodes[3]->id()]);
489     $expected_nids = [$book->id(), $nodes[0]->id(), $nodes[1]->id(), $nodes[2]->id(), $nodes[4]->id()];
490     $this->assertEqual(count($options), count($expected_nids));
491     $diff = array_diff($expected_nids, array_keys($options));
492     $this->assertTrue(empty($diff), 'Found all expected option keys after excluding Node 3');
493   }
494
495   /**
496    * Tests the book navigation block when an access module is installed.
497    */
498   public function testNavigationBlockOnAccessModuleInstalled() {
499     $this->drupalLogin($this->adminUser);
500     $block = $this->drupalPlaceBlock('book_navigation', ['block_mode' => 'book pages']);
501
502     // Give anonymous users the permission 'node test view'.
503     $edit = [];
504     $edit[RoleInterface::ANONYMOUS_ID . '[node test view]'] = TRUE;
505     $this->drupalPostForm('admin/people/permissions/' . RoleInterface::ANONYMOUS_ID, $edit, t('Save permissions'));
506     $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users.");
507
508     // Create a book.
509     $this->createBook();
510
511     // Test correct display of the block to registered users.
512     $this->drupalLogin($this->webUser);
513     $this->drupalGet('node/' . $this->book->id());
514     $this->assertText($block->label(), 'Book navigation block is displayed to registered users.');
515     $this->drupalLogout();
516
517     // Test correct display of the block to anonymous users.
518     $this->drupalGet('node/' . $this->book->id());
519     $this->assertText($block->label(), 'Book navigation block is displayed to anonymous users.');
520
521     // Test the 'book pages' block_mode setting.
522     $this->drupalGet('<front>');
523     $this->assertNoText($block->label(), 'Book navigation block is not shown on non-book pages.');
524   }
525
526   /**
527    * Tests the access for deleting top-level book nodes.
528    */
529   public function testBookDelete() {
530     $node_storage = $this->container->get('entity.manager')->getStorage('node');
531     $nodes = $this->createBook();
532     $this->drupalLogin($this->adminUser);
533     $edit = [];
534
535     // Test access to delete top-level and child book nodes.
536     $this->drupalGet('node/' . $this->book->id() . '/outline/remove');
537     $this->assertResponse('403', 'Deleting top-level book node properly forbidden.');
538     $this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove'));
539     $node_storage->resetCache([$nodes[4]->id()]);
540     $node4 = $node_storage->load($nodes[4]->id());
541     $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.');
542
543     // Delete all child book nodes and retest top-level node deletion.
544     foreach ($nodes as $node) {
545       $nids[] = $node->id();
546     }
547     entity_delete_multiple('node', $nids);
548     $this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove'));
549     $node_storage->resetCache([$this->book->id()]);
550     $node = $node_storage->load($this->book->id());
551     $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
552
553     // Tests directly deleting a book parent.
554     $nodes = $this->createBook();
555     $this->drupalLogin($this->adminUser);
556     $this->drupalGet($this->book->urlInfo('delete-form'));
557     $this->assertRaw(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.', ['%title' => $this->book->label()]));
558     // Delete parent, and visit a child page.
559     $this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete'));
560     $this->drupalGet($nodes[0]->urlInfo());
561     $this->assertResponse(200);
562     $this->assertText($nodes[0]->label());
563     // The book parents should be updated.
564     $node_storage = \Drupal::entityTypeManager()->getStorage('node');
565     $node_storage->resetCache();
566     $child = $node_storage->load($nodes[0]->id());
567     $this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
568     // 3rd-level children should now be 2nd-level.
569     $second = $node_storage->load($nodes[1]->id());
570     $this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
571   }
572
573   /**
574    * Tests outline of a book.
575    */
576   public function testBookOutline() {
577     $this->drupalLogin($this->bookAuthor);
578
579     // Create new node not yet a book.
580     $empty_book = $this->drupalCreateNode(['type' => 'book']);
581     $this->drupalGet('node/' . $empty_book->id() . '/outline');
582     $this->assertNoLink(t('Book outline'), 'Book Author is not allowed to outline');
583
584     $this->drupalLogin($this->adminUser);
585     $this->drupalGet('node/' . $empty_book->id() . '/outline');
586     $this->assertRaw(t('Book outline'));
587     $this->assertOptionSelected('edit-book-bid', 0, 'Node does not belong to a book');
588     $this->assertNoLink(t('Remove from book outline'));
589
590     $edit = [];
591     $edit['book[bid]'] = '1';
592     $this->drupalPostForm('node/' . $empty_book->id() . '/outline', $edit, t('Add to book outline'));
593     $node = \Drupal::entityManager()->getStorage('node')->load($empty_book->id());
594     // Test the book array.
595     $this->assertEqual($node->book['nid'], $empty_book->id());
596     $this->assertEqual($node->book['bid'], $empty_book->id());
597     $this->assertEqual($node->book['depth'], 1);
598     $this->assertEqual($node->book['p1'], $empty_book->id());
599     $this->assertEqual($node->book['pid'], '0');
600
601     // Create new book.
602     $this->drupalLogin($this->bookAuthor);
603     $book = $this->createBookNode('new');
604
605     $this->drupalLogin($this->adminUser);
606     $this->drupalGet('node/' . $book->id() . '/outline');
607     $this->assertRaw(t('Book outline'));
608     $this->clickLink(t('Remove from book outline'));
609     $this->assertRaw(t('Are you sure you want to remove %title from the book hierarchy?', ['%title' => $book->label()]));
610
611     // Create a new node and set the book after the node was created.
612     $node = $this->drupalCreateNode(['type' => 'book']);
613     $edit = [];
614     $edit['book[bid]'] = $node->id();
615     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
616     $node = \Drupal::entityManager()->getStorage('node')->load($node->id());
617
618     // Test the book array.
619     $this->assertEqual($node->book['nid'], $node->id());
620     $this->assertEqual($node->book['bid'], $node->id());
621     $this->assertEqual($node->book['depth'], 1);
622     $this->assertEqual($node->book['p1'], $node->id());
623     $this->assertEqual($node->book['pid'], '0');
624
625     // Test the form itself.
626     $this->drupalGet('node/' . $node->id() . '/edit');
627     $this->assertOptionSelected('edit-book-bid', $node->id());
628   }
629
630   /**
631    * Tests that saveBookLink() returns something.
632    */
633   public function testSaveBookLink() {
634     $book_manager = \Drupal::service('book.manager');
635
636     // Mock a link for a new book.
637     $link = ['nid' => 1, 'has_children' => 0, 'original_bid' => 0, 'parent_depth_limit' => 8, 'pid' => 0, 'weight' => 0, 'bid' => 1];
638     $new = TRUE;
639
640     // Save the link.
641     $return = $book_manager->saveBookLink($link, $new);
642
643     // Add the link defaults to $link so we have something to compare to the return from saveBookLink().
644     $link += $book_manager->getLinkDefaults($link['nid']);
645
646     // Test the return from saveBookLink.
647     $this->assertEqual($return, $link);
648   }
649
650   /**
651    * Tests the listing of all books.
652    */
653   public function testBookListing() {
654     // Create a new book.
655     $this->createBook();
656
657     // Must be a user with 'node test view' permission since node_access_test is installed.
658     $this->drupalLogin($this->webUser);
659
660     // Load the book page and assert the created book title is displayed.
661     $this->drupalGet('book');
662
663     $this->assertText($this->book->label(), 'The book title is displayed on the book listing page.');
664   }
665
666   /**
667    * Tests the administrative listing of all books.
668    */
669   public function testAdminBookListing() {
670     // Create a new book.
671     $this->createBook();
672
673     // Load the book page and assert the created book title is displayed.
674     $this->drupalLogin($this->adminUser);
675     $this->drupalGet('admin/structure/book');
676     $this->assertText($this->book->label(), 'The book title is displayed on the administrative book listing page.');
677   }
678
679   /**
680    * Tests the administrative listing of all book pages in a book.
681    */
682   public function testAdminBookNodeListing() {
683     // Create a new book.
684     $this->createBook();
685     $this->drupalLogin($this->adminUser);
686
687     // Load the book page list and assert the created book title is displayed
688     // and action links are shown on list items.
689     $this->drupalGet('admin/structure/book/' . $this->book->id());
690     $this->assertText($this->book->label(), 'The book title is displayed on the administrative book listing page.');
691
692     $elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a');
693     $this->assertEqual($elements[0]->getText(), 'View', 'View link is found from the list.');
694   }
695
696   /**
697    * Ensure the loaded book in hook_node_load() does not depend on the user.
698    */
699   public function testHookNodeLoadAccess() {
700     \Drupal::service('module_installer')->install(['node_access_test']);
701
702     // Ensure that the loaded book in hook_node_load() does NOT depend on the
703     // current user.
704     $this->drupalLogin($this->bookAuthor);
705     $this->book = $this->createBookNode('new');
706     // Reset any internal static caching.
707     $node_storage = \Drupal::entityManager()->getStorage('node');
708     $node_storage->resetCache();
709
710     // Log in as user without access to the book node, so no 'node test view'
711     // permission.
712     // @see node_access_test_node_grants().
713     $this->drupalLogin($this->webUserWithoutNodeAccess);
714     $book_node = $node_storage->load($this->book->id());
715     $this->assertTrue(!empty($book_node->book));
716     $this->assertEqual($book_node->book['bid'], $this->book->id());
717
718     // Reset the internal cache to retrigger the hook_node_load() call.
719     $node_storage->resetCache();
720
721     $this->drupalLogin($this->webUser);
722     $book_node = $node_storage->load($this->book->id());
723     $this->assertTrue(!empty($book_node->book));
724     $this->assertEqual($book_node->book['bid'], $this->book->id());
725   }
726
727   /**
728    * Tests the book navigation block when book is unpublished.
729    *
730    * There was a fatal error with "Show block only on book pages" block mode.
731    */
732   public function testBookNavigationBlockOnUnpublishedBook() {
733     // Create a new book.
734     $this->createBook();
735
736     // Create administrator user.
737     $administratorUser = $this->drupalCreateUser(['administer blocks', 'administer nodes', 'bypass node access']);
738     $this->drupalLogin($administratorUser);
739
740     // Enable the block with "Show block only on book pages" mode.
741     $this->drupalPlaceBlock('book_navigation', ['block_mode' => 'book pages']);
742
743     // Unpublish book node.
744     $edit = [];
745     $this->drupalPostForm('node/' . $this->book->id() . '/edit', $edit, t('Save and unpublish'));
746
747     // Test node page.
748     $this->drupalGet('node/' . $this->book->id());
749     $this->assertText($this->book->label(), 'Unpublished book with "Show block only on book pages" book navigation settings.');
750   }
751
752 }