diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 4249946..42269cb 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -187,14 +187,6 @@ function book_menu() { 'route_name' => 'book_render', 'type' => MENU_SUGGESTED_ITEM, ); - $items['book/export/%/%node'] = array( - 'page callback' => 'book_export', - 'page arguments' => array(2, 3), - 'access callback' => 'book_export_access', - 'access arguments' => array(3), - 'type' => MENU_CALLBACK, - 'file' => 'book.pages.inc', - ); $items['node/%node/outline'] = array( 'title' => 'Outline', 'page callback' => 'book_outline', @@ -218,16 +210,6 @@ function book_menu() { } /** - * Access callback: Determines if the book export page is accessible. - * - * @param \Drupal\node\Plugin\Core\Entity\EntityInterface $node - * The node whose export page is to be viewed. - */ -function book_export_access(EntityInterface $node) { - return user_access('access printer-friendly version') && node_access('view', $node); -} - -/** * Access callback: Determines if the outline tab is accessible. * * Path: @@ -1135,68 +1117,6 @@ function template_preprocess_book_export_html(&$variables) { } /** - * Traverses the book tree to build printable or exportable output. - * - * During the traversal, the $visit_func() callback is applied to each node and - * is called recursively for each child of the node (in weight, title order). - * - * @param $tree - * A subtree of the book menu hierarchy, rooted at the current page. - * @param $visit_func - * A function callback to be called upon visiting a node in the tree. - * - * @return - * The output generated in visiting each node. - */ -function book_export_traverse($tree, $visit_func) { - $output = ''; - - foreach ($tree as $data) { - // Note- access checking is already performed when building the tree. - if ($node = node_load($data['link']['nid'])) { - $children = ''; - - if ($data['below']) { - $children = book_export_traverse($data['below'], $visit_func); - } - - if (!empty($visit_func)) { - $output .= call_user_func($visit_func, $node, $children); - } - else { - // Use the default function. - $output .= book_node_export($node, $children); - } - } - } - - return $output; -} - -/** - * Generates printer-friendly HTML for a node. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node that will be output. - * @param $children - * (optional) All the rendered child nodes within the current node. Defaults - * to an empty string. - * - * @return - * The HTML generated for the given node. - * - * @see book_export_traverse() - */ -function book_node_export(EntityInterface $node, $children = '') { - $build = node_view($node, 'print'); - unset($build['#theme']); - // @todo Rendering should happen in the template using render(). - $node->rendered = drupal_render($build); - $book_node_export_html = array('#theme' => 'book_node_export_html', '#node' => $node, '#children' => $children ); - return drupal_render($book_node_export_html); -} - -/** * Prepares variables for single node export templates. * * Default template: book-node-export-html.html.twig. diff --git a/core/modules/book/book.pages.inc b/core/modules/book/book.pages.inc index 4cd7e25..d05a855 100644 --- a/core/modules/book/book.pages.inc +++ b/core/modules/book/book.pages.inc @@ -10,81 +10,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** - * Page callback: Generates representations of a book page and its children. - * - * The function delegates the generation of output to helper functions. The - * function name is derived by prepending 'book_export_' to the given output - * type. So, e.g., a type of 'html' results in a call to the function - * book_export_html(). - * - * @param $type - * A string encoding the type of output requested. The following types are - * currently supported in book module: - * - html: Printer-friendly HTML. - * Other types may be supported in contributed modules. - * @param \Drupal\node\Plugin\Core\Entity\EntityInterface $node - * The node to export. - * - * @return - * A string representing the node and its children in the book hierarchy in a - * format determined by the $type parameter. - * - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - * - * @see book_menu() - */ -function book_export($type, EntityInterface $node) { - $type = drupal_strtolower($type); - - $export_function = 'book_export_' . $type; - - if (function_exists($export_function)) { - print call_user_func($export_function, $node); - } - else { - drupal_set_message(t('Unknown export format.')); - throw new NotFoundHttpException(); - } -} - -/** - * Generates HTML for export when invoked by book_export(). - * - * The given node is embedded to its absolute depth in a top level section. For - * example, a child node with depth 2 in the hierarchy is contained in - * (otherwise empty)
elements corresponding to depth 0 and depth 1. - * This is intended to support WYSIWYG output - e.g., level 3 sections always - * look like level 3 sections, no matter their depth relative to the node - * selected to be exported as printer-friendly HTML. - * - * @param \Drupal\node\Plugin\Core\Entity\Node - * The node to export. - * - * @return - * A string containing HTML representing the node and its children in - * the book hierarchy. - * - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ -function book_export_html(EntityInterface $node) { - if (user_access('access printer-friendly version')) { - if (isset($node->book)) { - $tree = book_menu_subtree_data($node->book); - $contents = book_export_traverse($tree, 'book_node_export'); - $book_exported_html = array('#theme' => 'book_export_html', '#title' => $node->label(), '#contents' => $contents, '#depth' => $node->book['depth']); - return drupal_render($book_exported_html); - } - else { - throw new NotFoundHttpException(); - } - } - else { - throw new AccessDeniedHttpException(); - } -} - -/** * Page callback: Shows the outline form for a single node. * * @param \Drupal\Core\Entity\EntityInterface $node diff --git a/core/modules/book/book.routing.yml b/core/modules/book/book.routing.yml index 6c8b010..bf6f170 100644 --- a/core/modules/book/book.routing.yml +++ b/core/modules/book/book.routing.yml @@ -18,3 +18,13 @@ book_settings: _form: 'Drupal\book\BookSettingsForm' requirements: _permission: 'administer site configuration' + +book_export: + pattern: '/book/export/{type}/{node}' + defaults: + _controller: '\Drupal\book\Controller\BookController::bookExport' + options: + _access_mode: 'ALL' + requirements: + _permission: 'access printer-friendly version' + _entity_access: 'node.view' diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml index 9d8c140..042df96 100644 --- a/core/modules/book/book.services.yml +++ b/core/modules/book/book.services.yml @@ -2,3 +2,6 @@ services: book.manager: class: Drupal\book\BookManager arguments: ['@database', '@plugin.manager.entity'] + book.export: + class: Drupal\book\BookExport + arguments: ['@plugin.manager.entity'] diff --git a/core/modules/book/lib/Drupal/book/BookExport.php b/core/modules/book/lib/Drupal/book/BookExport.php new file mode 100644 index 0000000..62a5148 --- /dev/null +++ b/core/modules/book/lib/Drupal/book/BookExport.php @@ -0,0 +1,140 @@ +nodeStorage = $entityManager->getStorageController('node'); + $this->nodeRender = $entityManager->getRenderController('node'); + } + + /** + * Generates HTML for export when invoked by book_export(). + * + * The given node is embedded to its absolute depth in a top level section. For + * example, a child node with depth 2 in the hierarchy is contained in + * (otherwise empty)
elements corresponding to depth 0 and depth 1. + * This is intended to support WYSIWYG output - e.g., level 3 sections always + * look like level 3 sections, no matter their depth relative to the node + * selected to be exported as printer-friendly HTML. + * + * @param \Drupal\node\NodeInterface $node + * The node to export. + * + * @throws \Exception + * Thrown when the node was not attached to a book. + * + * @return array + * A render array representing the HTML for a node and its children in the + * book hierarchy. + */ + public function bookExportHtml(NodeInterface $node) { + if (!isset($node->book)) { + throw new \Exception(); + } + + $tree = book_menu_subtree_data($node->book); + $contents = $this->exportTraverse($tree, array($this, 'bookNodeExport')); + return array( + '#theme' => 'book_export_html', + '#title' => $node->label(), + '#contents' => $contents, + '#depth' => $node->book['depth'], + ); + } + + /** + * Traverses the book tree to build printable or exportable output. + * + * During the traversal, the callback is applied to each node and is called + * recursively for each child of the node (in weight, title order). + * + * @param array $tree + * A subtree of the book menu hierarchy, rooted at the current page. + * @param callable $callable + * A callback to be called upon visiting a node in the tree. + * + * @return string + * The output generated in visiting each node. + */ + protected function exportTraverse(array $tree, $callable) { + // If there is no valid callable, use the default callback. + $callable = !empty($callable) ? $callable : array($this, 'bookNodeExport'); + + $output = ''; + foreach ($tree as $data) { + // Note- access checking is already performed when building the tree. + if ($node = $this->nodeStorage->load($data['link']['nid'])) { + $children = $data['below'] ? $this->exportTraverse($data['below'], $callable) : ''; + + $callable_output = call_user_func($callable, $node, $children); + $output .= drupal_render($callable_output); + } + } + + return $output; + } + + /** + * Generates printer-friendly HTML for a node. + * + * @param \Drupal\node\NodeInterface $node + * The node that will be output. + * @param string $children + * (optional) All the rendered child nodes within the current node. Defaults + * to an empty string. + * + * @return array + * A render array for the exported HTML of a given node. + * + * @see \Drupal\book\BookExport::exportTraverse() + */ + protected function bookNodeExport(NodeInterface $node, $children = '') { + $build = $this->nodeRender->view($node, 'print', NULL); + unset($build['#theme']); + + // @todo Rendering should happen in the template using render(). + $node->rendered = drupal_render($build); + return array( + '#theme' => 'book_node_export_html', + '#node' => $node, + '#children' => $children, + ); + } + +} diff --git a/core/modules/book/lib/Drupal/book/Controller/BookController.php b/core/modules/book/lib/Drupal/book/Controller/BookController.php index 7b10eaf..b7b665f 100644 --- a/core/modules/book/lib/Drupal/book/Controller/BookController.php +++ b/core/modules/book/lib/Drupal/book/Controller/BookController.php @@ -1,4 +1,5 @@ get('book.manager')); - } + protected $bookExport; /** * Constructs a BookController object. + * + * @param \Drupal\book\BookManager $bookManager + * The book manager. + * @param \Drupal\book\BookExport $bookExport + * The book export service. */ - public function __construct(BookManager $bookManager) { + public function __construct(BookManager $bookManager, BookExport $bookExport) { $this->bookManager = $bookManager; + $this->bookExport = $bookExport; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('book.manager'), + $container->get('book.export') + ); } /** @@ -48,7 +69,6 @@ public function adminOverview() { $rows = array(); $headers = array(t('Book'), t('Operations')); - // Add any recognized books to the table list. foreach ($this->bookManager->getAllBooks() as $book) { $row = array( @@ -67,8 +87,12 @@ public function adminOverview() { ); $rows[] = $row; } - $table = array('#theme' => 'table', '#header' => $headers, '#rows' => $rows, '#empty' => t('No books available.')); - return drupal_render($table); + return array( + '#theme' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => t('No books available.'), + ); } /** @@ -82,8 +106,45 @@ public function bookRender() { foreach ($this->bookManager->getAllBooks() as $book) { $book_list[] = l($book['title'], $book['href'], $book['options']); } - $item_list = array('#theme' => 'item_list' , '#items' => $book_list); - return drupal_render($item_list); + return array( + '#theme' => 'item_list', + '#items' => $book_list, + ); + } + + /** + * Generates representations of a book page and its children. + * + * The method delegates the generation of output to helper methods. The method + * name is derived by prepending 'bookExport' to the camelized form of given + * output type. For example, a type of 'html' results in a call to the method + * bookExportHtml(). + * + * @param string $type + * A string encoding the type of output requested. The following types are + * currently supported in book module: + * - html: Printer-friendly HTML. + * Other types may be supported in contributed modules. + * @param \Drupal\node\NodeInterface $node + * The node to export. + * + * @return array + * A render array representing the node and its children in the book + * hierarchy in a format determined by the $type parameter. + * + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function bookExport($type, NodeInterface $node) { + $method = 'bookExport' . Container::camelize($type); + + // @todo Convert the custom export functionality to serializer. + if (!method_exists($this->bookExport, $method)) { + drupal_set_message(t('Unknown export format.')); + throw new NotFoundHttpException(); + } + + $exported_book = $this->bookExport->{$method}($node); + return new Response(drupal_render($exported_book)); } } diff --git a/core/modules/node/lib/Drupal/node/NodeBCDecorator.php b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php index b65bc4e..a492fb7 100644 --- a/core/modules/node/lib/Drupal/node/NodeBCDecorator.php +++ b/core/modules/node/lib/Drupal/node/NodeBCDecorator.php @@ -15,6 +15,13 @@ class NodeBCDecorator extends EntityBCDecorator implements NodeInterface { /** + * The NodeInterface object being decorated. + * + * @var \Drupal\node\NodeInterface + */ + protected $decorated; + + /** * {@inheritdoc} */ public function getType() { @@ -145,4 +152,11 @@ public function setRevisionAuthorId($uid) { return $this->decorated->setRevisionAuthorId($uid); } + /** + * {@inheritdoc} + */ + public function prepareLangcode() { + return $this->decorated->prepareLangcode(); + } + } diff --git a/core/modules/node/lib/Drupal/node/NodeInterface.php b/core/modules/node/lib/Drupal/node/NodeInterface.php index a9acd34..9414e8c 100644 --- a/core/modules/node/lib/Drupal/node/NodeInterface.php +++ b/core/modules/node/lib/Drupal/node/NodeInterface.php @@ -194,4 +194,12 @@ public function getRevisionAuthor(); */ public function setRevisionAuthorId($uid); + /** + * Prepares the langcode for a node. + * + * @return string + * The langcode for this node. + */ + public function prepareLangcode(); + } diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php index a79c796..5f6f5a9 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php @@ -11,6 +11,8 @@ use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Language\Language; +use Drupal\Core\Session\AccountInterface; use Drupal\node\NodeInterface; use Drupal\node\NodeBCDecorator; @@ -152,6 +154,40 @@ public function getType() { /** * {@inheritdoc} */ + public function access($operation = 'view', AccountInterface $account = NULL) { + if ($operation == 'create') { + return parent::access($operation, $account); + } + + return \Drupal::entityManager() + ->getAccessController($this->entityType) + ->access($this, $operation, $this->prepareLangcode(), $account); + } + + /** + * {@inheritdoc} + */ + public function prepareLangcode() { + $langcode = $this->language()->id; + // If the Language module is enabled, try to use the language from content + // negotiation. + if (\Drupal::moduleHandler()->moduleExists('language')) { + // Load languages the node exists in. + $node_translations = $this->getTranslationLanguages(); + // Load the language from content negotiation. + $content_negotiation_langcode = \Drupal::languageManager()->getLanguage(Language::TYPE_CONTENT)->id; + // If there is a translation available, use it. + if (isset($node_translations[$content_negotiation_langcode])) { + $langcode = $content_negotiation_langcode; + } + } + return $langcode; + } + + + /** + * {@inheritdoc} + */ public function getTitle() { return $this->get('title')->value; } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index a47b72b..500776f 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -2045,19 +2045,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) { // If no language code was provided, default to the node's langcode. if (empty($langcode)) { - $langcode = $node->language()->id; - // If the Language module is enabled, try to use the language from content - // negotiation. - if (module_exists('language')) { - // Load languages the node exists in. - $node_translations = $node->getTranslationLanguages(); - // Load the language from content negotiation. - $content_negotiation_langcode = language(Language::TYPE_CONTENT)->id; - // If there is a translation available, use it. - if (isset($node_translations[$content_negotiation_langcode])) { - $langcode = $content_negotiation_langcode; - } - } + $langcode = $node->prepareLangcode(); } return $access_controller->access($node, $op, $langcode, $account); }