drupalPlaceBlock('system_breadcrumb_block'); // node_access_test requires a node_access_rebuild(). node_access_rebuild(); // Create users. $this->bookAuthor = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books')); $this->webUser = $this->drupalCreateUser(array('access printer-friendly version', 'node test view')); $this->webUserWithoutNodeAccess = $this->drupalCreateUser(array('access printer-friendly version')); $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')); } /** * Creates a new book with a page hierarchy. */ function createBook() { // Create new book. $this->drupalLogin($this->bookAuthor); $this->book = $this->createBookNode('new'); $book = $this->book; /* * Add page hierarchy to book. * Book * |- Node 0 * |- Node 1 * |- Node 2 * |- Node 3 * |- Node 4 */ $nodes = array(); $nodes[] = $this->createBookNode($book->id()); // Node 0. $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 1. $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 2. $nodes[] = $this->createBookNode($book->id()); // Node 3. $nodes[] = $this->createBookNode($book->id()); // Node 4. $this->drupalLogout(); return $nodes; } /** * Tests saving the book outline on an empty book. */ function testEmptyBook() { // Create a new empty book. $this->drupalLogin($this->bookAuthor); $book = $this->createBookNode('new'); $this->drupalLogout(); // Log in as a user with access to the book outline and save the form. $this->drupalLogin($this->adminUser); $this->drupalPostForm('admin/structure/book/' . $book->id(), array(), t('Save book pages')); $this->assertText(t('Updated book @book.', array('@book' => $book->label()))); } /** * Tests book functionality through node interfaces. */ function testBook() { // Create new book. $nodes = $this->createBook(); $book = $this->book; $this->drupalLogin($this->webUser); // Check that book pages display along with the correct outlines and // previous/next links. $this->checkBookNode($book, array($nodes[0], $nodes[3], $nodes[4]), FALSE, FALSE, $nodes[0], array()); $this->checkBookNode($nodes[0], array($nodes[1], $nodes[2]), $book, $book, $nodes[1], array($book)); $this->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2], array($book, $nodes[0])); $this->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3], array($book, $nodes[0])); $this->checkBookNode($nodes[3], NULL, $nodes[2], $book, $nodes[4], array($book)); $this->checkBookNode($nodes[4], NULL, $nodes[3], $book, FALSE, array($book)); $this->drupalLogout(); $this->drupalLogin($this->bookAuthor); // Check the presence of expected cache tags. $this->drupalGet('node/add/book'); $this->assertCacheTag('config:book.settings'); /* * Add Node 5 under Node 3. * Book * |- Node 0 * |- Node 1 * |- Node 2 * |- Node 3 * |- Node 5 * |- Node 4 */ $nodes[] = $this->createBookNode($book->id(), $nodes[3]->book['nid']); // Node 5. $this->drupalLogout(); $this->drupalLogin($this->webUser); // Verify the new outline - make sure we don't get stale cached data. $this->checkBookNode($nodes[3], array($nodes[5]), $nodes[2], $book, $nodes[5], array($book)); $this->checkBookNode($nodes[4], NULL, $nodes[5], $book, FALSE, array($book)); $this->drupalLogout(); // Create a second book, and move an existing book page into it. $this->drupalLogin($this->bookAuthor); $other_book = $this->createBookNode('new'); $node = $this->createBookNode($book->id()); $edit = array('book[bid]' => $other_book->id()); $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); $this->drupalLogout(); $this->drupalLogin($this->webUser); // Check that the nodes in the second book are displayed correctly. // First we must set $this->book to the second book, so that the // correct regex will be generated for testing the outline. $this->book = $other_book; $this->checkBookNode($other_book, array($node), FALSE, FALSE, $node, array()); $this->checkBookNode($node, NULL, $other_book, $other_book, FALSE, array($other_book)); // Test that we can save a book programatically. $this->drupalLogin($this->bookAuthor); $book = $this->createBookNode('new'); $book->save(); } /** * Checks the outline of sub-pages; previous, up, and next. * * Also checks the printer friendly version of the outline. * * @param \Drupal\Core\Entity\EntityInterface $node * Node to check. * @param $nodes * Nodes that should be in outline. * @param $previous * (optional) Previous link node. Defaults to FALSE. * @param $up * (optional) Up link node. Defaults to FALSE. * @param $next * (optional) Next link node. Defaults to FALSE. * @param array $breadcrumb * The nodes that should be displayed in the breadcrumb. */ function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = FALSE, $next = FALSE, array $breadcrumb) { // $number does not use drupal_static as it should not be reset // since it uniquely identifies each call to checkBookNode(). static $number = 0; $this->drupalGet('node/' . $node->id()); // Check outline structure. if ($nodes !== NULL) { $this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node @number outline confirmed.', array('@number' => $number))); } else { $this->pass(format_string('Node %number does not have outline.', array('%number' => $number))); } // Check previous, up, and next links. if ($previous) { /** @var \Drupal\Core\Url $url */ $url = $previous->urlInfo(); $url->setOptions(array('attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page')))); $text = SafeMarkup::format(' @label', array('@label' => $previous->label())); $this->assertRaw(\Drupal::l($text, $url), 'Previous page link found.'); } if ($up) { /** @var \Drupal\Core\Url $url */ $url = $up->urlInfo(); $url->setOptions(array('attributes' => array('title' => t('Go to parent page')))); $this->assertRaw(\Drupal::l('Up', $url), 'Up page link found.'); } if ($next) { /** @var \Drupal\Core\Url $url */ $url = $next->urlInfo(); $url->setOptions(array('attributes' => array('rel' => array('next'), 'title' => t('Go to next page')))); $text = SafeMarkup::format('@label ', array('@label' => $next->label())); $this->assertRaw(\Drupal::l($text, $url), 'Next page link found.'); } // Compute the expected breadcrumb. $expected_breadcrumb = array(); $expected_breadcrumb[] = \Drupal::url(''); foreach ($breadcrumb as $a_node) { $expected_breadcrumb[] = $a_node->url(); } // Fetch links in the current breadcrumb. $links = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a'); $got_breadcrumb = array(); foreach ($links as $link) { $got_breadcrumb[] = (string) $link['href']; } // Compare expected and got breadcrumbs. $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.'); // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->id()); $this->assertText($node->label(), 'Printer friendly title found.'); $this->assertRaw($node->body->processed, 'Printer friendly body found.'); $number++; } /** * Creates a regular expression to check for the sub-nodes in the outline. * * @param array $nodes * An array of nodes to check in outline. * * @return string * A regular expression that locates sub-nodes of the outline. */ function generateOutlinePattern($nodes) { $outline = ''; foreach ($nodes as $node) { $outline .= '(node\/' . $node->id() . ')(.*?)(' . $node->label() . ')(.*?)'; } return '/