Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/mod/book/ -> lib.php (source)

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