diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 61e3701..0fe58ed 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -195,13 +195,9 @@ function book_menu() { ); $items['node/%node/outline'] = array( 'title' => 'Outline', - 'page callback' => 'book_outline', - 'page arguments' => array(1), - 'access callback' => '_book_outline_access', - 'access arguments' => array(1), + 'route_name' => 'book_outline', 'type' => MENU_LOCAL_TASK, 'weight' => 2, - 'file' => 'book.pages.inc', ); $items['node/%node/outline/remove'] = array( 'title' => 'Remove from outline', @@ -250,20 +246,8 @@ function _book_outline_access(EntityInterface $node) { * @see book_menu() */ function _book_outline_remove_access(EntityInterface $node) { - return _book_node_is_removable($node) && _book_outline_access($node); -} - -/** - * Determines if a node can be removed from the book. - * - * A node can be removed from a book if it is actually in a book and it either - * is not a top-level page or is a top-level page with no children. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node to remove from the outline. - */ -function _book_node_is_removable(EntityInterface $node) { - return (!empty($node->book['bid']) && (($node->book['bid'] != $node->nid) || !$node->book['has_children'])); + return Drupal::service('book.manager')->bookNodeIsRemovable($node) + && _book_outline_access($node); } /** @@ -302,6 +286,9 @@ function book_get_books() { * @see book_pick_book_nojs_submit() */ function book_form_node_form_alter(&$form, &$form_state, $form_id) { + // Get BookManager service + $book_manager = Drupal::service('book.manager'); + $node = $form_state['controller']->getEntity(); $access = user_access('administer book outlines'); if (!$access) { @@ -310,9 +297,8 @@ function book_form_node_form_alter(&$form, &$form_state, $form_id) { $access = TRUE; } } - if ($access) { - _book_add_form_elements($form, $form_state, $node); + $book_manager->bookAddFormElements($form, $form_state, $node); // Since the "Book" dropdown can't trigger a form submission when // JavaScript is disabled, add a submit button to do that. book.admin.css hides // this button when JavaScript is enabled. @@ -329,6 +315,15 @@ function book_form_node_form_alter(&$form, &$form_state, $form_id) { } /** + * Implements hook_form_BASE_FORM_ID_alter() for the book_outline form. + */ +function book_form_book_outline_alter(&$form, &$form_state, $form_id) { + // Dynamically set title + $node = reset($form_state['build_info']['args']); + drupal_set_title($node->label()); +} + +/** * Form submission handler for node_form(). * * This handler is run when JavaScript is disabled. It triggers the form to @@ -400,92 +395,6 @@ function _book_parent_select($book_link) { } /** - * Builds the common elements of the book form for the node and outline forms. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The node whose form is being viewed. - */ -function _book_add_form_elements(&$form, &$form_state, EntityInterface $node) { - // If the form is being processed during the Ajax callback of our book bid - // dropdown, then $form_state will hold the value that was selected. - if (isset($form_state['values']['book'])) { - $node->book = $form_state['values']['book']; - } - - $form['book'] = array( - '#type' => 'details', - '#title' => t('Book outline'), - '#weight' => 10, - '#collapsed' => TRUE, - '#group' => 'advanced', - '#attributes' => array( - 'class' => array('book-outline-form'), - ), - '#attached' => array( - 'library' => array(array('book', 'drupal.book')), - ), - '#tree' => TRUE, - ); - foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) { - $form['book'][$key] = array( - '#type' => 'value', - '#value' => $node->book[$key], - ); - } - - $form['book']['plid'] = _book_parent_select($node->book); - - // @see _book_admin_table_tree(). The weight may be larger than 15. - $form['book']['weight'] = array( - '#type' => 'weight', - '#title' => t('Weight'), - '#default_value' => $node->book['weight'], - '#delta' => max(15, abs($node->book['weight'])), - '#weight' => 5, - '#description' => t('Pages at a given level are ordered first by weight and then by title.'), - ); - $options = array(); - $nid = isset($node->nid) ? $node->nid : 'new'; - - if (isset($node->nid) && ($nid == $node->book['original_bid']) && ($node->book['parent_depth_limit'] == 0)) { - // This is the top level node in a maximum depth book and thus cannot be moved. - $options[$node->nid] = $node->label(); - } - else { - foreach (book_get_books() as $book) { - $options[$book['nid']] = $book['title']; - } - } - - if (user_access('create new books') && ($nid == 'new' || ($nid != $node->book['original_bid']))) { - // The node can become a new book, if it is not one already. - $options = array($nid => t('- Create a new book -')) + $options; - } - if (!$node->book['mlid']) { - // The node is not currently in the hierarchy. - $options = array(0 => t('- None -')) + $options; - } - - // Add a drop-down to select the destination book. - $form['book']['bid'] = array( - '#type' => 'select', - '#title' => t('Book'), - '#default_value' => $node->book['bid'], - '#options' => $options, - '#access' => (bool) $options, - '#description' => t('Your page will be a part of the selected book.'), - '#weight' => -5, - '#attributes' => array('class' => array('book-title-select')), - '#ajax' => array( - 'callback' => 'book_form_update', - 'wrapper' => 'edit-book-plid-wrapper', - 'effect' => 'fade', - 'speed' => 'fast', - ), - ); -} - -/** * Renders a new parent page select element when the book selection changes. * * This function is called via Ajax when the selected book is changed on a node @@ -871,6 +780,9 @@ function book_node_predelete(EntityInterface $node) { * Implements hook_node_prepare(). */ function book_node_prepare(EntityInterface $node) { + // Get BookManager service + $book_manager = Drupal::service('book.manager'); + // Prepare defaults for the add/edit form. if (empty($node->book) && (user_access('add content to books') || user_access('administer book outlines'))) { $node->book = array(); @@ -886,7 +798,8 @@ function book_node_prepare(EntityInterface $node) { } } // Set defaults. - $node->book += _book_link_defaults(!empty($node->nid) ? $node->nid : 'new'); + $node_ref = !empty($node->nid) ? $node->nid : 'new'; + $node->book += $book_manager->bookLinkDefaults($node_ref); } else { if (isset($node->book['bid']) && !isset($node->book['original_bid'])) { @@ -895,24 +808,11 @@ function book_node_prepare(EntityInterface $node) { } // Find the depth limit for the parent select. if (isset($node->book['bid']) && !isset($node->book['parent_depth_limit'])) { - $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book); + $node->book['parent_depth_limit'] = $book_manager->bookParentDepthLimit($node->book); } } /** - * Finds the depth limit for items in the parent select. - * - * @param $book_link - * A fully loaded menu link that is part of the book hierarchy. - * - * @return - * The depth limit for items in the parent select. - */ -function _book_parent_depth_limit($book_link) { - return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($book_link) : 0); -} - -/** * Implements hook_form_FORM_ID_alter() for node_delete_confirm(). * * Alters the confirm form for a single node deletion. @@ -931,19 +831,6 @@ function book_form_node_delete_confirm_alter(&$form, $form_state) { } /** - * Returns an array with default values for a book page's menu link. - * - * @param $nid - * The ID of the node whose menu link is being created. - * - * @return - * The default values for the menu link. - */ -function _book_link_defaults($nid) { - return array('original_bid' => 0, 'menu_name' => '', 'nid' => $nid, 'bid' => 0, 'router_path' => 'node/%', 'plid' => 0, 'mlid' => 0, 'has_children' => 0, 'weight' => 0, 'module' => 'book', 'options' => array()); -} - - /** * Implements hook_preprocess_HOOK() for block.html.twig. */ function book_preprocess_block(&$variables) { diff --git a/core/modules/book/book.pages.inc b/core/modules/book/book.pages.inc index 67c4c99..9486786 100644 --- a/core/modules/book/book.pages.inc +++ b/core/modules/book/book.pages.inc @@ -84,111 +84,17 @@ function book_export_html(EntityInterface $node) { } /** - * Page callback: Shows the outline form for a single node. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The book node for which to show the outline. - * - * @return string - * A HTML-formatted string with the outline form for a single node. - * - * @see book_menu() - */ -function book_outline(EntityInterface $node) { - drupal_set_title($node->label()); - return drupal_get_form('book_outline_form', $node); -} - -/** - * Form constructor for the book outline form. - * - * Allows handling of all book outline operations via the outline tab. - * - * @param \Drupal\Core\Entity\EntityInterface $node - * The book node for which to show the outline. - * - * @see book_outline_form_submit() - * @see book_remove_button_submit() - * @ingroup forms - */ -function book_outline_form($form, &$form_state, EntityInterface $node) { - if (!isset($node->book)) { - // The node is not part of any book yet - set default options. - $node->book = _book_link_defaults($node->nid); - } - else { - $node->book['original_bid'] = $node->book['bid']; - } - - // Find the depth limit for the parent select. - if (!isset($node->book['parent_depth_limit'])) { - $node->book['parent_depth_limit'] = _book_parent_depth_limit($node->book); - } - $form['#node'] = $node; - $form['#id'] = 'book-outline'; - _book_add_form_elements($form, $form_state, $node); - - $form['update'] = array( - '#type' => 'submit', - '#value' => $node->book['original_bid'] ? t('Update book outline') : t('Add to book outline'), - '#weight' => 15, - ); - - $form['remove'] = array( - '#type' => 'submit', - '#value' => t('Remove from book outline'), - '#access' => _book_node_is_removable($node), - '#weight' => 20, - '#submit' => array('book_remove_button_submit'), - ); - - return $form; -} - -/** * Form submission handler for book_outline_form(). * * Redirects to removal confirmation form. * - * @see book_outline_form_submit() + * @see \Drupal\book\Form\BookOutlineForm::buildForm() */ function book_remove_button_submit($form, &$form_state) { $form_state['redirect'] = 'node/' . $form['#node']->nid . '/outline/remove'; } /** - * Form submission handler for book_outline_form(). - * - * @see book_remove_button_submit() - */ -function book_outline_form_submit($form, &$form_state) { - $node = $form['#node']; - $form_state['redirect'] = "node/" . $node->nid; - $book_link = $form_state['values']['book']; - if (!$book_link['bid']) { - drupal_set_message(t('No changes were made')); - - return; - } - - $book_link['menu_name'] = book_menu_name($book_link['bid']); - $node->book = $book_link; - if (_book_update_outline($node)) { - if ($node->book['parent_mismatch']) { - // This will usually only happen when JS is disabled. - drupal_set_message(t('The post has been added to the selected book. You may now position it relative to other pages.')); - $form_state['redirect'] = "node/" . $node->nid . "/outline"; - } - else { - drupal_set_message(t('The book outline has been updated.')); - } - } - else { - drupal_set_message(t('There was an error adding the post to the book.'), 'error'); - } -} - -/** * Form constructor to confirm removal of a node from a book. * * @param \Drupal\Core\Entity\EntityInterface $node @@ -217,7 +123,7 @@ function book_remove_form($form, &$form_state, EntityInterface $node) { */ function book_remove_form_submit($form, &$form_state) { $node = $form['#node']; - if (_book_node_is_removable($node)) { + if (Drupal::service('book.manager')->bookNodeIsRemovable($node)) { menu_link_delete($node->book['mlid']); db_delete('book') ->condition('nid', $node->nid) diff --git a/core/modules/book/book.routing.yml b/core/modules/book/book.routing.yml index 6c8b010..9c501fe 100644 --- a/core/modules/book/book.routing.yml +++ b/core/modules/book/book.routing.yml @@ -18,3 +18,10 @@ book_settings: _form: 'Drupal\book\BookSettingsForm' requirements: _permission: 'administer site configuration' + +book_outline: + pattern: 'node/{node}/outline' + defaults: + _form: 'Drupal\book\Form\BookOutlineForm' + requirements: + _permission: 'administer book outlines' diff --git a/core/modules/book/lib/Drupal/book/BookManager.php b/core/modules/book/lib/Drupal/book/BookManager.php index b037429..e093f49 100644 --- a/core/modules/book/lib/Drupal/book/BookManager.php +++ b/core/modules/book/lib/Drupal/book/BookManager.php @@ -8,6 +8,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityManager; +use Drupal\Core\Entity\EntityInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -94,4 +95,129 @@ protected function loadBooks() { } } + /** + * Returns an array with default values for a book page's menu link. + * + * @param $nid + * The ID of the node whose menu link is being created. + * + * @return + * The default values for the menu link. + */ + public function bookLinkDefaults($nid) { + return array('original_bid' => 0, 'menu_name' => '', 'nid' => $nid, + 'bid' => 0, 'router_path' => 'node/%', 'plid' => 0, 'mlid' => 0, + 'has_children' => 0, 'weight' => 0, 'module' => 'book', + 'options' => array()); + } + + /** + * Finds the depth limit for items in the parent select. + * + * @param $book_link + * A fully loaded menu link that is part of the book hierarchy. + * + * @return + * The depth limit for items in the parent select. + */ + public function bookParentDepthLimit($book_link) { + return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($book_link) : 0); + } + + /** + * Builds the common elements of the book form for the node and outline forms. + * + * @param \Drupal\Core\Entity\EntityInterface $node + * The node whose form is being viewed. + */ + public function bookAddFormElements(&$form, &$form_state, EntityInterface $node) { + // If the form is being processed during the Ajax callback of our book bid + // dropdown, then $form_state will hold the value that was selected. + if (isset($form_state['values']['book'])) { + $node->book = $form_state['values']['book']; + } + $form['book'] = array( + '#type' => 'details', + '#title' => t('Book outline'), + '#weight' => 10, + '#collapsed' => TRUE, + '#group' => 'advanced', + '#attributes' => array( + 'class' => array('book-outline-form'), + ), + '#attached' => array( + 'library' => array(array('book', 'drupal.book')), + ), + '#tree' => TRUE, + ); + foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) { + $form['book'][$key] = array( + '#type' => 'value', + '#value' => $node->book[$key], + ); + } + + $form['book']['plid'] = _book_parent_select($node->book); + + // @see _book_admin_table_tree(). The weight may be larger than 15. + $form['book']['weight'] = array( + '#type' => 'weight', + '#title' => t('Weight'), + '#default_value' => $node->book['weight'], + '#delta' => max(15, abs($node->book['weight'])), + '#weight' => 5, + '#description' => t('Pages at a given level are ordered first by weight and then by title.'), + ); + $options = array(); + $nid = isset($node->nid) ? $node->nid : 'new'; + if (isset($node->nid) && ($nid == $node->book['original_bid']) && ($node->book['parent_depth_limit'] == 0)) { + // This is the top level node in a maximum depth book and thus cannot be moved. + $options[$node->nid] = $node->label(); + } + else { + foreach (book_get_books() as $book) { + $options[$book['nid']] = $book['title']; + } + } + + if (user_access('create new books') && ($nid == 'new' || ($nid != $node->book['original_bid']))) { + // The node can become a new book, if it is not one already. + $options = array($nid => t('- Create a new book -')) + $options; + } + if (!$node->book['mlid']) { + // The node is not currently in the hierarchy. + $options = array(0 => t('- None -')) + $options; + } + + // Add a drop-down to select the destination book. + $form['book']['bid'] = array( + '#type' => 'select', + '#title' => t('Book'), + '#default_value' => $node->book['bid'], + '#options' => $options, + '#access' => (bool) $options, + '#description' => t('Your page will be a part of the selected book.'), + '#weight' => -5, + '#attributes' => array('class' => array('book-title-select')), + '#ajax' => array( + 'callback' => 'book_form_update', + 'wrapper' => 'edit-book-plid-wrapper', + 'effect' => 'fade', + 'speed' => 'fast', + ), + ); + } + + /** + * Determines if a node can be removed from the book. + * + * A node can be removed from a book if it is actually in a book and it either + * is not a top-level page or is a top-level page with no children. + * + * @param \Drupal\Core\Entity\EntityInterface $node + * The node to remove from the outline. + */ + public function bookNodeIsRemovable(EntityInterface $node) { + return (!empty($node->book['bid']) && (($node->book['bid'] != $node->nid) || !$node->book['has_children'])); + } } diff --git a/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php new file mode 100644 index 0000000..9cbfcfa --- /dev/null +++ b/core/modules/book/lib/Drupal/book/Form/BookOutlineForm.php @@ -0,0 +1,124 @@ +bookManager = $bookManager; + } + + /** + * This method lets us inject the services this class needs. + * + * Only inject services that are actually needed. Which services + * are needed will vary by the controller. + */ + public static function create(ContainerInterface $container) { + return new static($container->get('book.manager')); + } + + /** + * Implements \Drupal\Core\Form\FormInterface::getFormID(). + */ + public function getFormID() { + return 'book_outline'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state, EntityBCDecorator $node = NULL) { + if (!isset($node->book)) { + // The node is not part of any book yet - set default options. + $node->book = $this->bookManager->bookLinkDefaults($node->nid); + } + else { + $node->book['original_bid'] = $node->book['bid']; + } + + // Find the depth limit for the parent select. + if (!isset($node->book['parent_depth_limit'])) { + $node->book['parent_depth_limit'] = $this->bookManager->bookParentDepthLimit($node->book); + } + $form['#node'] = $node; + $form['#id'] = 'book-outline'; + $this->bookManager->bookAddFormElements($form, $form_state, $node); + + $form['update'] = array( + '#type' => 'submit', + '#value' => $node->book['original_bid'] ? t('Update book outline') : t('Add to book outline'), + '#weight' => 15, + ); + + $form['remove'] = array( + '#type' => 'submit', + '#value' => t('Remove from book outline'), + '#access' => $this->bookManager->bookNodeIsRemovable($node), + '#weight' => 20, + '#submit' => array('book_remove_button_submit'), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) {} + + /** + * {@inheritdoc} + * + * @see book_remove_button_submit() + */ + public function submitForm(array &$form, array &$form_state) { + $node = $form['#node']; + $form_state['redirect'] = "node/" . $node->nid; + $book_link = $form_state['values']['book']; + if (!$book_link['bid']) { + drupal_set_message(t('No changes were made')); + + return; + } + + $book_link['menu_name'] = book_menu_name($book_link['bid']); + $node->book = $book_link; + if (_book_update_outline($node)) { + if ($node->book['parent_mismatch']) { + // This will usually only happen when JS is disabled. + drupal_set_message(t('The post has been added to the selected book. You may now position it relative to other pages.')); + $form_state['redirect'] = "node/" . $node->nid . "/outline"; + } + else { + drupal_set_message(t('The book outline has been updated.')); + } + } + else { + drupal_set_message(t('There was an error adding the post to the book.'), 'error'); + } + } +}