Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
/mod/book/ -> lib.php (source)

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Book module core interaction API
  19   *
  20   * @package    mod_book
  21   * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die;
  26  
  27  /**
  28   * Returns list of available numbering types
  29   * @return array
  30   */
  31  function book_get_numbering_types() {
  32      global $CFG; // required for the include
  33  
  34      require_once (__DIR__.'/locallib.php');
  35  
  36      return array (
  37          BOOK_NUM_NONE       => get_string('numbering0', 'mod_book'),
  38          BOOK_NUM_NUMBERS    => get_string('numbering1', 'mod_book'),
  39          BOOK_NUM_BULLETS    => get_string('numbering2', 'mod_book'),
  40          BOOK_NUM_INDENTED   => get_string('numbering3', 'mod_book')
  41      );
  42  }
  43  
  44  /**
  45   * Returns list of available navigation link types.
  46   * @return array
  47   */
  48  function book_get_nav_types() {
  49      require_once (__DIR__.'/locallib.php');
  50  
  51      return array (
  52          BOOK_LINK_TOCONLY   => get_string('navtoc', 'mod_book'),
  53          BOOK_LINK_IMAGE     => get_string('navimages', 'mod_book'),
  54          BOOK_LINK_TEXT      => get_string('navtext', 'mod_book'),
  55      );
  56  }
  57  
  58  /**
  59   * Returns list of available navigation link CSS classes.
  60   * @return array
  61   */
  62  function book_get_nav_classes() {
  63      return array ('navtoc', 'navimages', 'navtext');
  64  }
  65  
  66  /**
  67   * Add book instance.
  68   *
  69   * @param stdClass $data
  70   * @param stdClass $mform
  71   * @return int new book instance id
  72   */
  73  function book_add_instance($data, $mform) {
  74      global $DB;
  75  
  76      $data->timecreated = time();
  77      $data->timemodified = $data->timecreated;
  78      if (!isset($data->customtitles)) {
  79          $data->customtitles = 0;
  80      }
  81  
  82      $id = $DB->insert_record('book', $data);
  83  
  84      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
  85      \core_completion\api::update_completion_date_event($data->coursemodule, 'book', $id, $completiontimeexpected);
  86  
  87      return $id;
  88  }
  89  
  90  /**
  91   * Update book instance.
  92   *
  93   * @param stdClass $data
  94   * @param stdClass $mform
  95   * @return bool true
  96   */
  97  function book_update_instance($data, $mform) {
  98      global $DB;
  99  
 100      $data->timemodified = time();
 101      $data->id = $data->instance;
 102      if (!isset($data->customtitles)) {
 103          $data->customtitles = 0;
 104      }
 105  
 106      $DB->update_record('book', $data);
 107  
 108      $book = $DB->get_record('book', array('id'=>$data->id));
 109      $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
 110  
 111      $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
 112      \core_completion\api::update_completion_date_event($data->coursemodule, 'book', $book->id, $completiontimeexpected);
 113  
 114      return true;
 115  }
 116  
 117  /**
 118   * Delete book instance by activity id
 119   *
 120   * @param int $id
 121   * @return bool success
 122   */
 123  function book_delete_instance($id) {
 124      global $DB;
 125  
 126      if (!$book = $DB->get_record('book', array('id'=>$id))) {
 127          return false;
 128      }
 129  
 130      $cm = get_coursemodule_from_instance('book', $id);
 131      \core_completion\api::update_completion_date_event($cm->id, 'book', $id, null);
 132  
 133      $DB->delete_records('book_chapters', array('bookid'=>$book->id));
 134      $DB->delete_records('book', array('id'=>$book->id));
 135  
 136      return true;
 137  }
 138  
 139  /**
 140   * Given a course and a time, this module should find recent activity
 141   * that has occurred in book activities and print it out.
 142   *
 143   * @param stdClass $course
 144   * @param bool $viewfullnames
 145   * @param int $timestart
 146   * @return bool true if there was output, or false is there was none
 147   */
 148  function book_print_recent_activity($course, $viewfullnames, $timestart) {
 149      return false;  //  True if anything was printed, otherwise false
 150  }
 151  
 152  /**
 153   * This function is used by the reset_course_userdata function in moodlelib.
 154   * @param $data the data submitted from the reset course.
 155   * @return array status array
 156   */
 157  function book_reset_userdata($data) {
 158      global $DB;
 159      // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
 160      // See MDL-9367.
 161  
 162      $status = [];
 163  
 164      if (!empty($data->reset_book_tags)) {
 165          // Loop through the books and remove the tags from the chapters.
 166          if ($books = $DB->get_records('book', array('course' => $data->courseid))) {
 167              foreach ($books as $book) {
 168                  if (!$cm = get_coursemodule_from_instance('book', $book->id)) {
 169                      continue;
 170                  }
 171  
 172                  $context = context_module::instance($cm->id);
 173                  core_tag_tag::delete_instances('mod_book', null, $context->id);
 174              }
 175          }
 176  
 177  
 178          $status[] = [
 179              'component' => get_string('modulenameplural', 'book'),
 180              'item' => get_string('tagsdeleted', 'book'),
 181              'error' => false
 182          ];
 183      }
 184  
 185      return $status;
 186  }
 187  
 188  /**
 189   * The elements to add the course reset form.
 190   *
 191   * @param moodleform $mform
 192   */
 193  function book_reset_course_form_definition(&$mform) {
 194      $mform->addElement('header', 'bookheader', get_string('modulenameplural', 'book'));
 195      $mform->addElement('checkbox', 'reset_book_tags', get_string('removeallbooktags', 'book'));
 196  }
 197  
 198  /**
 199   * No cron in book.
 200   *
 201   * @return bool
 202   */
 203  function book_cron () {
 204      return true;
 205  }
 206  
 207  /**
 208   * No grading in book.
 209   *
 210   * @param int $bookid
 211   * @return null
 212   */
 213  function book_grades($bookid) {
 214      return null;
 215  }
 216  
 217  /**
 218   * @deprecated since Moodle 3.8
 219   */
 220  function book_scale_used() {
 221      throw new coding_exception('book_scale_used() can not be used anymore. Plugins can implement ' .
 222          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
 223  }
 224  
 225  /**
 226   * Checks if scale is being used by any instance of book
 227   *
 228   * This is used to find out if scale used anywhere
 229   *
 230   * @param int $scaleid
 231   * @return bool true if the scale is used by any book
 232   */
 233  function book_scale_used_anywhere($scaleid) {
 234      return false;
 235  }
 236  
 237  /**
 238   * Return read actions.
 239   *
 240   * Note: This is not used by new logging system. Event with
 241   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
 242   *       be considered as view action.
 243   *
 244   * @return array
 245   */
 246  function book_get_view_actions() {
 247      global $CFG; // necessary for includes
 248  
 249      $return = array('view', 'view all');
 250  
 251      $plugins = core_component::get_plugin_list('booktool');
 252      foreach ($plugins as $plugin => $dir) {
 253          if (file_exists("$dir/lib.php")) {
 254              require_once("$dir/lib.php");
 255          }
 256          $function = 'booktool_'.$plugin.'_get_view_actions';
 257          if (function_exists($function)) {
 258              if ($actions = $function()) {
 259                  $return = array_merge($return, $actions);
 260              }
 261          }
 262      }
 263  
 264      return $return;
 265  }
 266  
 267  /**
 268   * Return write actions.
 269   *
 270   * Note: This is not used by new logging system. Event with
 271   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
 272   *       will be considered as post action.
 273   *
 274   * @return array
 275   */
 276  function book_get_post_actions() {
 277      global $CFG; // necessary for includes
 278  
 279      $return = array('update');
 280  
 281      $plugins = core_component::get_plugin_list('booktool');
 282      foreach ($plugins as $plugin => $dir) {
 283          if (file_exists("$dir/lib.php")) {
 284              require_once("$dir/lib.php");
 285          }
 286          $function = 'booktool_'.$plugin.'_get_post_actions';
 287          if (function_exists($function)) {
 288              if ($actions = $function()) {
 289                  $return = array_merge($return, $actions);
 290              }
 291          }
 292      }
 293  
 294      return $return;
 295  }
 296  
 297  /**
 298   * Supported features
 299   *
 300   * @param string $feature FEATURE_xx constant for requested feature
 301   * @return mixed True if module supports feature, false if not, null if doesn't know
 302   */
 303  function book_supports($feature) {
 304      switch($feature) {
 305          case FEATURE_MOD_ARCHETYPE:           return MOD_ARCHETYPE_RESOURCE;
 306          case FEATURE_GROUPS:                  return false;
 307          case FEATURE_GROUPINGS:               return false;
 308          case FEATURE_MOD_INTRO:               return true;
 309          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
 310          case FEATURE_GRADE_HAS_GRADE:         return false;
 311          case FEATURE_GRADE_OUTCOMES:          return false;
 312          case FEATURE_BACKUP_MOODLE2:          return true;
 313          case FEATURE_SHOW_DESCRIPTION:        return true;
 314  
 315          default: return null;
 316      }
 317  }
 318  
 319  /**
 320   * Adds module specific settings to the settings block
 321   *
 322   * @param settings_navigation $settingsnav The settings navigation object
 323   * @param navigation_node $booknode The node to add module settings to
 324   * @return void
 325   */
 326  function book_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $booknode) {
 327      global $USER, $PAGE, $OUTPUT;
 328  
 329      if ($booknode->children->count() > 0) {
 330          $firstkey = $booknode->children->get_key_list()[0];
 331      } else {
 332          $firstkey = null;
 333      }
 334  
 335      $params = $PAGE->url->params();
 336  
 337      if ($PAGE->cm->modname === 'book' and !empty($params['id']) and !empty($params['chapterid'])
 338              and has_capability('mod/book:edit', $PAGE->cm->context)) {
 339          if (!empty($USER->editing)) {
 340              $string = get_string("turneditingoff");
 341              $edit = '0';
 342          } else {
 343              $string = get_string("turneditingon");
 344              $edit = '1';
 345          }
 346          $url = new moodle_url('/mod/book/view.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid'], 'edit'=>$edit, 'sesskey'=>sesskey()));
 347          $editnode = navigation_node::create($string, $url, navigation_node::TYPE_SETTING);
 348          $booknode->add_node($editnode, $firstkey);
 349          $PAGE->set_button($OUTPUT->single_button($url, $string));
 350      }
 351  
 352      $plugins = core_component::get_plugin_list('booktool');
 353      foreach ($plugins as $plugin => $dir) {
 354          if (file_exists("$dir/lib.php")) {
 355              require_once("$dir/lib.php");
 356          }
 357          $function = 'booktool_'.$plugin.'_extend_settings_navigation';
 358          if (function_exists($function)) {
 359              $function($settingsnav, $booknode);
 360          }
 361      }
 362  }
 363  
 364  
 365  /**
 366   * Lists all browsable file areas
 367   * @param object $course
 368   * @param object $cm
 369   * @param object $context
 370   * @return array
 371   */
 372  function book_get_file_areas($course, $cm, $context) {
 373      $areas = array();
 374      $areas['chapter'] = get_string('chapters', 'mod_book');
 375      return $areas;
 376  }
 377  
 378  /**
 379   * File browsing support for book module chapter area.
 380   * @param object $browser
 381   * @param object $areas
 382   * @param object $course
 383   * @param object $cm
 384   * @param object $context
 385   * @param string $filearea
 386   * @param int $itemid
 387   * @param string $filepath
 388   * @param string $filename
 389   * @return object file_info instance or null if not found
 390   */
 391  function book_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
 392      global $CFG, $DB;
 393  
 394      // note: 'intro' area is handled in file_browser automatically
 395  
 396      if (!has_capability('mod/book:read', $context)) {
 397          return null;
 398      }
 399  
 400      if ($filearea !== 'chapter') {
 401          return null;
 402      }
 403  
 404      require_once (__DIR__.'/locallib.php');
 405  
 406      if (is_null($itemid)) {
 407          return new book_file_info($browser, $course, $cm, $context, $areas, $filearea);
 408      }
 409  
 410      $fs = get_file_storage();
 411      $filepath = is_null($filepath) ? '/' : $filepath;
 412      $filename = is_null($filename) ? '.' : $filename;
 413      if (!$storedfile = $fs->get_file($context->id, 'mod_book', $filearea, $itemid, $filepath, $filename)) {
 414          return null;
 415      }
 416  
 417      // modifications may be tricky - may cause caching problems
 418      $canwrite = has_capability('mod/book:edit', $context);
 419  
 420      $chaptername = $DB->get_field('book_chapters', 'title', array('bookid'=>$cm->instance, 'id'=>$itemid));
 421      $chaptername = format_string($chaptername, true, array('context'=>$context));
 422  
 423      $urlbase = $CFG->wwwroot.'/pluginfile.php';
 424      return new file_info_stored($browser, $context, $storedfile, $urlbase, $chaptername, true, true, $canwrite, false);
 425  }
 426  
 427  /**
 428   * Serves the book attachments. Implements needed access control ;-)
 429   *
 430   * @param stdClass $course course object
 431   * @param cm_info $cm course module object
 432   * @param context $context context object
 433   * @param string $filearea file area
 434   * @param array $args extra arguments
 435   * @param bool $forcedownload whether or not force download
 436   * @param array $options additional options affecting the file serving
 437   * @return bool false if file not found, does not return if found - just send the file
 438   */
 439  function book_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
 440      global $CFG, $DB;
 441  
 442      if ($context->contextlevel != CONTEXT_MODULE) {
 443          return false;
 444      }
 445  
 446      require_course_login($course, true, $cm);
 447  
 448      if ($filearea !== 'chapter') {
 449          return false;
 450      }
 451  
 452      if (!has_capability('mod/book:read', $context)) {
 453          return false;
 454      }
 455  
 456      $chid = (int)array_shift($args);
 457  
 458      if (!$book = $DB->get_record('book', array('id'=>$cm->instance))) {
 459          return false;
 460      }
 461  
 462      if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chid, 'bookid'=>$book->id))) {
 463          return false;
 464      }
 465  
 466      if ($chapter->hidden and !has_capability('mod/book:viewhiddenchapters', $context)) {
 467          return false;
 468      }
 469  
 470      // Download the contents of a chapter as an html file.
 471      if ($args[0] == 'index.html') {
 472          $filename = "index.html";
 473  
 474          // We need to rewrite the pluginfile URLs so the media filters can work.
 475          $content = file_rewrite_pluginfile_urls($chapter->content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
 476                                                  $chapter->id);
 477          $formatoptions = new stdClass;
 478          $formatoptions->noclean = true;
 479          $formatoptions->overflowdiv = true;
 480          $formatoptions->context = $context;
 481  
 482          $content = format_text($content, $chapter->contentformat, $formatoptions);
 483  
 484          // Remove @@PLUGINFILE@@/.
 485          $options = array('reverse' => true);
 486          $content = file_rewrite_pluginfile_urls($content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
 487                                                  $chapter->id, $options);
 488          $content = str_replace('@@PLUGINFILE@@/', '', $content);
 489  
 490          $titles = "";
 491          // Format the chapter titles.
 492          if (!$book->customtitles) {
 493              require_once (__DIR__.'/locallib.php');
 494              $chapters = book_preload_chapters($book);
 495  
 496              if (!$chapter->subchapter) {
 497                  $currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
 498                  // Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
 499                  $titles = "<h3>$currtitle</h3>";
 500              } else {
 501                  $currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
 502                  $currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
 503                  // Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
 504                  $titles = "<h3>$currtitle</h3>";
 505                  $titles .= "<h4>$currsubtitle</h4>";
 506              }
 507          }
 508  
 509          $content = $titles . $content;
 510  
 511          send_file($content, $filename, 0, 0, true, true);
 512      } else {
 513          $fs = get_file_storage();
 514          $relativepath = implode('/', $args);
 515          $fullpath = "/$context->id/mod_book/chapter/$chid/$relativepath";
 516          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
 517              return false;
 518          }
 519  
 520          // Nasty hack because we do not have file revisions in book yet.
 521          $lifetime = $CFG->filelifetime;
 522          if ($lifetime > 60 * 10) {
 523              $lifetime = 60 * 10;
 524          }
 525  
 526          // Finally send the file.
 527          send_stored_file($file, $lifetime, 0, $forcedownload, $options);
 528      }
 529  }
 530  
 531  /**
 532   * Return a list of page types
 533   *
 534   * @param string $pagetype current page type
 535   * @param stdClass $parentcontext Block's parent context
 536   * @param stdClass $currentcontext Current context of block
 537   * @return array
 538   */
 539  function book_page_type_list($pagetype, $parentcontext, $currentcontext) {
 540      $module_pagetype = array('mod-book-*'=>get_string('page-mod-book-x', 'mod_book'));
 541      return $module_pagetype;
 542  }
 543  
 544  /**
 545   * Export book resource contents
 546   *
 547   * @param  stdClass $cm     Course module object
 548   * @param  string $baseurl  Base URL for file downloads
 549   * @return array of file content
 550   */
 551  function book_export_contents($cm, $baseurl) {
 552      global $DB;
 553  
 554      $contents = array();
 555      $context = context_module::instance($cm->id);
 556  
 557      $book = $DB->get_record('book', array('id' => $cm->instance), '*', MUST_EXIST);
 558  
 559      $fs = get_file_storage();
 560  
 561      $chapters = $DB->get_records('book_chapters', array('bookid' => $book->id), 'pagenum');
 562  
 563      $structure = array();
 564      $currentchapter = 0;
 565  
 566      foreach ($chapters as $chapter) {
 567          if ($chapter->hidden && !has_capability('mod/book:viewhiddenchapters', $context)) {
 568              continue;
 569          }
 570  
 571          // Generate the book structure.
 572          $thischapter = array(
 573              "title"     => format_string($chapter->title, true, array('context' => $context)),
 574              "href"      => $chapter->id . "/index.html",
 575              "level"     => 0,
 576              "hidden"    => $chapter->hidden,
 577              "subitems"  => array()
 578          );
 579  
 580          // Main chapter.
 581          if (!$chapter->subchapter) {
 582              $currentchapter = $chapter->pagenum;
 583              $structure[$currentchapter] = $thischapter;
 584          } else {
 585              // Subchapter.
 586              $thischapter['level'] = 1;
 587              $structure[$currentchapter]["subitems"][] = $thischapter;
 588          }
 589  
 590          // Export the chapter contents.
 591  
 592          // Main content (html).
 593          $filename = 'index.html';
 594          $chapterindexfile = array();
 595          $chapterindexfile['type']         = 'file';
 596          $chapterindexfile['filename']     = $filename;
 597          // Each chapter in a subdirectory.
 598          $chapterindexfile['filepath']     = "/{$chapter->id}/";
 599          $chapterindexfile['filesize']     = 0;
 600          $chapterindexfile['fileurl']      = moodle_url::make_webservice_pluginfile_url(
 601                      $context->id, 'mod_book', 'chapter', $chapter->id, '/', 'index.html')->out(false);
 602          $chapterindexfile['timecreated']  = $chapter->timecreated;
 603          $chapterindexfile['timemodified'] = $chapter->timemodified;
 604          $chapterindexfile['content']      = format_string($chapter->title, true, array('context' => $context));
 605          $chapterindexfile['sortorder']    = 0;
 606          $chapterindexfile['userid']       = null;
 607          $chapterindexfile['author']       = null;
 608          $chapterindexfile['license']      = null;
 609          $chapterindexfile['tags']         = \core_tag\external\util::get_item_tags('mod_book', 'book_chapters', $chapter->id);
 610          $contents[] = $chapterindexfile;
 611  
 612          // Chapter files (images usually).
 613          $files = $fs->get_area_files($context->id, 'mod_book', 'chapter', $chapter->id, 'sortorder DESC, id ASC', false);
 614          foreach ($files as $fileinfo) {
 615              $file = array();
 616              $file['type']         = 'file';
 617              $file['filename']     = $fileinfo->get_filename();
 618              $file['filepath']     = "/{$chapter->id}" . $fileinfo->get_filepath();
 619              $file['filesize']     = $fileinfo->get_filesize();
 620              $file['fileurl']      = moodle_url::make_webservice_pluginfile_url(
 621                                          $context->id, 'mod_book', 'chapter', $chapter->id,
 622                                          $fileinfo->get_filepath(), $fileinfo->get_filename())->out(false);
 623              $file['timecreated']  = $fileinfo->get_timecreated();
 624              $file['timemodified'] = $fileinfo->get_timemodified();
 625              $file['sortorder']    = $fileinfo->get_sortorder();
 626              $file['userid']       = $fileinfo->get_userid();
 627              $file['author']       = $fileinfo->get_author();
 628              $file['license']      = $fileinfo->get_license();
 629              $file['mimetype']     = $fileinfo->get_mimetype();
 630              $file['isexternalfile'] = $fileinfo->is_external_file();
 631              if ($file['isexternalfile']) {
 632                  $file['repositorytype'] = $fileinfo->get_repository_type();
 633              }
 634              $contents[] = $file;
 635          }
 636      }
 637  
 638      // First content is the structure in encoded JSON format.
 639      $structurefile = array();
 640      $structurefile['type']         = 'content';
 641      $structurefile['filename']     = 'structure';
 642      $structurefile['filepath']     = "/";
 643      $structurefile['filesize']     = 0;
 644      $structurefile['fileurl']      = null;
 645      $structurefile['timecreated']  = $book->timecreated;
 646      $structurefile['timemodified'] = $book->timemodified;
 647      $structurefile['content']      = json_encode(array_values($structure));
 648      $structurefile['sortorder']    = 0;
 649      $structurefile['userid']       = null;
 650      $structurefile['author']       = null;
 651      $structurefile['license']      = null;
 652  
 653      // Add it as first element.
 654      array_unshift($contents, $structurefile);
 655  
 656      return $contents;
 657  }
 658  
 659  /**
 660   * Mark the activity completed (if required) and trigger the course_module_viewed event.
 661   *
 662   * @param  stdClass $book       book object
 663   * @param  stdClass $chapter    chapter object
 664   * @param  bool $islaschapter   is the las chapter of the book?
 665   * @param  stdClass $course     course object
 666   * @param  stdClass $cm         course module object
 667   * @param  stdClass $context    context object
 668   * @since Moodle 3.0
 669   */
 670  function book_view($book, $chapter, $islastchapter, $course, $cm, $context) {
 671  
 672      // First case, we are just opening the book.
 673      if (empty($chapter)) {
 674          \mod_book\event\course_module_viewed::create_from_book($book, $context)->trigger();
 675  
 676      } else {
 677          \mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter)->trigger();
 678  
 679          if ($islastchapter) {
 680              // We cheat a bit here in assuming that viewing the last page means the user viewed the whole book.
 681              $completion = new completion_info($course);
 682              $completion->set_module_viewed($cm);
 683          }
 684      }
 685  }
 686  
 687  /**
 688   * Check if the module has any update that affects the current user since a given time.
 689   *
 690   * @param  cm_info $cm course module data
 691   * @param  int $from the time to check updates from
 692   * @param  array $filter  if we need to check only specific updates
 693   * @return stdClass an object with the different type of areas indicating if they were updated or not
 694   * @since Moodle 3.2
 695   */
 696  function book_check_updates_since(cm_info $cm, $from, $filter = array()) {
 697      global $DB;
 698  
 699      $context = $cm->context;
 700      $updates = new stdClass();
 701      if (!has_capability('mod/book:read', $context)) {
 702          return $updates;
 703      }
 704      $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
 705  
 706      $select = 'bookid = :id AND (timecreated > :since1 OR timemodified > :since2)';
 707      $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
 708      if (!has_capability('mod/book:viewhiddenchapters', $context)) {
 709          $select .= ' AND hidden = 0';
 710      }
 711      $updates->entries = (object) array('updated' => false);
 712      $entries = $DB->get_records_select('book_chapters', $select, $params, '', 'id');
 713      if (!empty($entries)) {
 714          $updates->entries->updated = true;
 715          $updates->entries->itemids = array_keys($entries);
 716      }
 717  
 718      return $updates;
 719  }
 720  
 721  /**
 722   * Get icon mapping for font-awesome.
 723   */
 724  function mod_book_get_fontawesome_icon_map() {
 725      return [
 726          'mod_book:chapter' => 'fa-bookmark-o',
 727          'mod_book:nav_prev' => 'fa-arrow-left',
 728          'mod_book:nav_sep' => 'fa-minus',
 729          'mod_book:add' => 'fa-plus',
 730          'mod_book:nav_next' => 'fa-arrow-right',
 731          'mod_book:nav_exit' => 'fa-arrow-up',
 732      ];
 733  }
 734  
 735  /**
 736   * This function receives a calendar event and returns the action associated with it, or null if there is none.
 737   *
 738   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
 739   * is not displayed on the block.
 740   *
 741   * @param calendar_event $event
 742   * @param \core_calendar\action_factory $factory
 743   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
 744   * @return \core_calendar\local\event\entities\action_interface|null
 745   */
 746  function mod_book_core_calendar_provide_event_action(calendar_event $event,
 747                                                       \core_calendar\action_factory $factory,
 748                                                       int $userid = 0) {
 749      global $USER;
 750  
 751      if (empty($userid)) {
 752          $userid = $USER->id;
 753      }
 754  
 755      $cm = get_fast_modinfo($event->courseid, $userid)->instances['book'][$event->instance];
 756  
 757      if (!$cm->uservisible) {
 758          // The module is not visible to the user for any reason.
 759          return null;
 760      }
 761  
 762      $context = context_module::instance($cm->id);
 763  
 764      if (!has_capability('mod/book:read', $context, $userid)) {
 765          return null;
 766      }
 767  
 768      $completion = new \completion_info($cm->get_course());
 769  
 770      $completiondata = $completion->get_data($cm, false, $userid);
 771  
 772      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
 773          return null;
 774      }
 775  
 776      return $factory->create_instance(
 777          get_string('view'),
 778          new \moodle_url('/mod/book/view.php', ['id' => $cm->id]),
 779          1,
 780          true
 781      );
 782  }