You are here

course_book.module in Course 8.2

File

modules/course_book/course_book.module
View source
<?php

/**
 * Course book admin form.
 */
function course_book_admin($form, &$form_state) {
  $form = array();
  $form['book_intro']['#markup'] = t("Course books allow you to use Drupal Books as course objects.");
  $form['course_book_default_node_type'] = array(
    '#title' => t('Default node type.'),
    '#type' => 'select',
    '#options' => node_type_get_names(),
    '#default_value' => variable_get('course_book_default_node_type', 'book'),
    '#description' => t("Sets the default node type for book objects. This can be overridden whilst assigning a book to a course outline."),
  );
  return system_settings_form($form);
}

/**
 * Implements hook_menu().
 */
function course_book_menu() {
  $items = array();
  $items['admin/course/book'] = array(
    'access arguments' => array(
      'administer courses',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'course_book_admin',
    ),
    'type' => MENU_LOCAL_TASK,
    'title' => 'Book',
  );
  return $items;
}

/**
 * Overrides a course outline list item.
 *
 * @param array $item
 *   A course outline list item. The structure mirrors an array element from
 *   the $items param from theme_item_list().
 * @param CourseObject $courseObject
 *   The instantiated course object that has an outline item to be overridden.
 * @param string $type
 *   The type of override to perform. Can be:
 *   - all_pages: Displays a nested item list of book pages, with all items
 *     fully expanded.
 *   - active_tree: Displays the active menu tree, mirroring the core book
 *     outline on book pages. Additionally, the number of pages are appended to
 *     the course outline item title, to indicate there are pages when not a
 *     page within the active tree.
 *   - count: Displays the book title, with the number of pages in the book.
 */
function course_book_override_outline_list_item(&$item, Drupal\course\Entity\CourseObject $courseObject, $type) {
  if ($courseObject
    ->getComponent() == 'book') {
    if ($bid = $courseObject
      ->getInstanceId()) {

      // Alter the book outline item differently, depending on configured type.
      switch ($type) {
        case 'all_pages':

          // If users do not have access to take the book course object, display
          // only titles instaed of links.
          $links = $courseObject
            ->access('take');

          // Get the top level item in the tree (the book item).
          $book_items = course_book_items($bid, $links);
          $book_tree = reset($book_items);
          if ($book_tree['children']) {

            // Add a fully expanded list of children below the existing course
            // book outline item.
            $item['children'] = $book_tree['children'];
          }
          break;
        case 'active_tree':
          $book_node = \Drupal\node\Entity\Node::load($bid);
          if (!empty($book_node->book)) {

            /* @var $book_manager Drupal\book\BookManager */
            $book_manager = \Drupal::service('book.manager');
            $tree = $book_manager
              ->bookTreeAllData($bid);

            // There should only be one element at the top level.
            $data = array_shift($tree);
            if ($data['below']) {
              $output = $book_manager
                ->bookTreeOutput($data['below']);

              // Append to the existing book outline item's data output, since we
              // don't have an array but already rendered active menu tree output.
              $item['book_children']['#markup'] = drupal_render($output);
              $item['book_children']['#weight'] = 100;
            }

            // Note we do not break here purposefully, to additionally append the
            // number of pages to the course outline item title. We do this
            // because when not a page within the active tree, no children items
            // display, so without this there is no indicator they are there.
          }
        case 'count':
          $count = course_book_count($bid);

          // @kludge replace outline object title with an appended version.
          $subject = $item['#markup'] ?? '';
          $pattern = $courseObject
            ->getTitle();
          if ($count > 1) {
            $replacement = t('@title (@count pages)', array(
              '@title' => $pattern,
              '@count' => $count,
            ));

            // Replace only the first instance of the title (in case the same
            // string also exists elsewhere in the data output.

            //$replaced = str_replace($pattern, $replacement, $subject);
            $replaced = preg_replace('/' . preg_quote($pattern, '/') . '/', $replacement, $subject, 1);
            $item['#markup'] = $replaced;
          }
          break;
      }
    }
  }
}

/**
 * Counts the number of book pages that are accessible to the current user.
 *
 * @param int $bid
 *   A book ID.
 * @param array $exclude
 *   (Optional) An array of node IDs. Any link whose ID is in this array
 *   will be excluded (along with its children).
 *
 * @return int
 *   The number of accessible pages in a book.
 */
function course_book_count($bid, $exclude = array()) {

  /* @var $book_manager Drupal\book\BookManager */
  $book_manager = \Drupal::service('book.manager');
  $toc = $book_manager
    ->getTableOfContents($bid, \Drupal::config('course_book.settings')
    ->get('depth', 100), $exclude);
  $count = count($toc);
  return $count;
}

/**
 * Gets a nested list of book items.
 *
 * @param int $bid
 *   A book ID.
 * @param bool $links
 *   Whether or not to render item links.
 * @param array $exclude
 *   (Optional) An array of menu link IDs. Any link whose mlid is in this array
 *   will be excluded (along with its children).
 * @param int $depth_limit
 *   Any link deeper than this value will be excluded (along with its children).
 *
 * @return array
 *   A nested array of book items, suitable for theme_item_list().
 */
function course_book_items($bid, $links = TRUE, $exclude = array(), $depth_limit = MENU_MAX_DEPTH) {
  $tree = menu_tree_all_data(book_menu_name($bid));
  $items = course_book_items_recurse($tree, $links, $exclude, $depth_limit);
  return $items;
}

/**
 * A recursive helper function for course_book_items().
 */
function course_book_items_recurse($tree, $links, $exclude, $depth_limit) {
  $items = array();
  foreach ($tree as $data) {
    if ($data['link']['depth'] > $depth_limit) {

      // Don't iterate through any links on this level.
      break;
    }
    if (!in_array($data['link']['nid'], $exclude)) {
      $link = array();

      // Build the current nested item - either a link or text.
      $text = truncate_utf8($data['link']['title'], 30, TRUE, TRUE);
      if ($links) {
        $path = $data['link']['href'];
        $link['data'] = l($text, $path);
      }
      else {
        $link['data'] = $text;
      }

      // Get children, if any.
      if ($data['below']) {
        $link['children'] = course_book_items_recurse($data['below'], $links, $exclude, $depth_limit);
      }
      $items[] = $link;
    }
  }
  return array_values($items);
}

/**
 * Implements hook_node_insert().
 *
 * Resave the book course object associated with this book page.
 *
 * We need to do this so that proper hooks get run to set content access.
 */
function course_book_node_insert($node) {
  if (!empty($node->book['bid']) && $node->book['bid'] != $node
    ->id()) {
    if ($courseObject = course_get_course_object('book', $node->book['bid'])) {
      $courseObject
        ->save();
    }
  }
}

Functions

Namesort descending Description
course_book_admin Course book admin form.
course_book_count Counts the number of book pages that are accessible to the current user.
course_book_items Gets a nested list of book items.
course_book_items_recurse A recursive helper function for course_book_items().
course_book_menu Implements hook_menu().
course_book_node_insert Implements hook_node_insert().
course_book_override_outline_list_item Overrides a course outline list item.