MDL-60828 user: Reset current page when filtering/searching users
[moodle.git] / mod / book / lib.php
blobdbd86890e7da0de1e6143b23e03835b122a6fe8d
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.
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/>.
17 /**
18 * Book module core interaction API
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
25 defined('MOODLE_INTERNAL') || die;
27 /**
28 * Returns list of available numbering types
29 * @return array
31 function book_get_numbering_types() {
32 global $CFG; // required for the include
34 require_once(__DIR__.'/locallib.php');
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')
44 /**
45 * Returns list of available navigation link types.
46 * @return array
48 function book_get_nav_types() {
49 require_once(__DIR__.'/locallib.php');
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'),
58 /**
59 * Returns list of available navigation link CSS classes.
60 * @return array
62 function book_get_nav_classes() {
63 return array ('navtoc', 'navimages', 'navtext');
66 /**
67 * Returns all other caps used in module
68 * @return array
70 function book_get_extra_capabilities() {
71 // used for group-members-only
72 return array('moodle/site:accessallgroups');
75 /**
76 * Add book instance.
78 * @param stdClass $data
79 * @param stdClass $mform
80 * @return int new book instance id
82 function book_add_instance($data, $mform) {
83 global $DB;
85 $data->timecreated = time();
86 $data->timemodified = $data->timecreated;
87 if (!isset($data->customtitles)) {
88 $data->customtitles = 0;
91 $id = $DB->insert_record('book', $data);
93 $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
94 \core_completion\api::update_completion_date_event($data->coursemodule, 'book', $id, $completiontimeexpected);
96 return $id;
99 /**
100 * Update book instance.
102 * @param stdClass $data
103 * @param stdClass $mform
104 * @return bool true
106 function book_update_instance($data, $mform) {
107 global $DB;
109 $data->timemodified = time();
110 $data->id = $data->instance;
111 if (!isset($data->customtitles)) {
112 $data->customtitles = 0;
115 $DB->update_record('book', $data);
117 $book = $DB->get_record('book', array('id'=>$data->id));
118 $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
120 $completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
121 \core_completion\api::update_completion_date_event($data->coursemodule, 'book', $book->id, $completiontimeexpected);
123 return true;
127 * Delete book instance by activity id
129 * @param int $id
130 * @return bool success
132 function book_delete_instance($id) {
133 global $DB;
135 if (!$book = $DB->get_record('book', array('id'=>$id))) {
136 return false;
139 $cm = get_coursemodule_from_instance('book', $id);
140 \core_completion\api::update_completion_date_event($cm->id, 'book', $id, null);
142 $DB->delete_records('book_chapters', array('bookid'=>$book->id));
143 $DB->delete_records('book', array('id'=>$book->id));
145 return true;
149 * Given a course and a time, this module should find recent activity
150 * that has occurred in book activities and print it out.
152 * @param stdClass $course
153 * @param bool $viewfullnames
154 * @param int $timestart
155 * @return bool true if there was output, or false is there was none
157 function book_print_recent_activity($course, $viewfullnames, $timestart) {
158 return false; // True if anything was printed, otherwise false
162 * This function is used by the reset_course_userdata function in moodlelib.
163 * @param $data the data submitted from the reset course.
164 * @return array status array
166 function book_reset_userdata($data) {
167 global $DB;
168 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
169 // See MDL-9367.
171 $status = [];
173 if (!empty($data->reset_book_tags)) {
174 // Loop through the books and remove the tags from the chapters.
175 if ($books = $DB->get_records('book', array('course' => $data->courseid))) {
176 foreach ($books as $book) {
177 if (!$cm = get_coursemodule_from_instance('book', $book->id)) {
178 continue;
181 $context = context_module::instance($cm->id);
182 core_tag_tag::delete_instances('mod_book', null, $context->id);
187 $status[] = [
188 'component' => get_string('modulenameplural', 'book'),
189 'item' => get_string('tagsdeleted', 'book'),
190 'error' => false
194 return $status;
198 * The elements to add the course reset form.
200 * @param moodleform $mform
202 function book_reset_course_form_definition(&$mform) {
203 $mform->addElement('header', 'bookheader', get_string('modulenameplural', 'book'));
204 $mform->addElement('checkbox', 'reset_book_tags', get_string('removeallbooktags', 'book'));
208 * No cron in book.
210 * @return bool
212 function book_cron () {
213 return true;
217 * No grading in book.
219 * @param int $bookid
220 * @return null
222 function book_grades($bookid) {
223 return null;
227 * This function returns if a scale is being used by one book
228 * it it has support for grading and scales. Commented code should be
229 * modified if necessary. See book, glossary or journal modules
230 * as reference.
232 * @param int $bookid
233 * @param int $scaleid
234 * @return boolean True if the scale is used by any journal
236 function book_scale_used($bookid, $scaleid) {
237 return false;
241 * Checks if scale is being used by any instance of book
243 * This is used to find out if scale used anywhere
245 * @param int $scaleid
246 * @return bool true if the scale is used by any book
248 function book_scale_used_anywhere($scaleid) {
249 return false;
253 * Return read actions.
255 * Note: This is not used by new logging system. Event with
256 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will
257 * be considered as view action.
259 * @return array
261 function book_get_view_actions() {
262 global $CFG; // necessary for includes
264 $return = array('view', 'view all');
266 $plugins = core_component::get_plugin_list('booktool');
267 foreach ($plugins as $plugin => $dir) {
268 if (file_exists("$dir/lib.php")) {
269 require_once("$dir/lib.php");
271 $function = 'booktool_'.$plugin.'_get_view_actions';
272 if (function_exists($function)) {
273 if ($actions = $function()) {
274 $return = array_merge($return, $actions);
279 return $return;
283 * Return write actions.
285 * Note: This is not used by new logging system. Event with
286 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
287 * will be considered as post action.
289 * @return array
291 function book_get_post_actions() {
292 global $CFG; // necessary for includes
294 $return = array('update');
296 $plugins = core_component::get_plugin_list('booktool');
297 foreach ($plugins as $plugin => $dir) {
298 if (file_exists("$dir/lib.php")) {
299 require_once("$dir/lib.php");
301 $function = 'booktool_'.$plugin.'_get_post_actions';
302 if (function_exists($function)) {
303 if ($actions = $function()) {
304 $return = array_merge($return, $actions);
309 return $return;
313 * Supported features
315 * @param string $feature FEATURE_xx constant for requested feature
316 * @return mixed True if module supports feature, false if not, null if doesn't know
318 function book_supports($feature) {
319 switch($feature) {
320 case FEATURE_MOD_ARCHETYPE: return MOD_ARCHETYPE_RESOURCE;
321 case FEATURE_GROUPS: return false;
322 case FEATURE_GROUPINGS: return false;
323 case FEATURE_MOD_INTRO: return true;
324 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
325 case FEATURE_GRADE_HAS_GRADE: return false;
326 case FEATURE_GRADE_OUTCOMES: return false;
327 case FEATURE_BACKUP_MOODLE2: return true;
328 case FEATURE_SHOW_DESCRIPTION: return true;
330 default: return null;
335 * Adds module specific settings to the settings block
337 * @param settings_navigation $settingsnav The settings navigation object
338 * @param navigation_node $booknode The node to add module settings to
339 * @return void
341 function book_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $booknode) {
342 global $USER, $PAGE, $OUTPUT;
344 if ($booknode->children->count() > 0) {
345 $firstkey = $booknode->children->get_key_list()[0];
346 } else {
347 $firstkey = null;
350 $params = $PAGE->url->params();
352 if ($PAGE->cm->modname === 'book' and !empty($params['id']) and !empty($params['chapterid'])
353 and has_capability('mod/book:edit', $PAGE->cm->context)) {
354 if (!empty($USER->editing)) {
355 $string = get_string("turneditingoff");
356 $edit = '0';
357 } else {
358 $string = get_string("turneditingon");
359 $edit = '1';
361 $url = new moodle_url('/mod/book/view.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid'], 'edit'=>$edit, 'sesskey'=>sesskey()));
362 $editnode = navigation_node::create($string, $url, navigation_node::TYPE_SETTING);
363 $booknode->add_node($editnode, $firstkey);
364 $PAGE->set_button($OUTPUT->single_button($url, $string));
367 $plugins = core_component::get_plugin_list('booktool');
368 foreach ($plugins as $plugin => $dir) {
369 if (file_exists("$dir/lib.php")) {
370 require_once("$dir/lib.php");
372 $function = 'booktool_'.$plugin.'_extend_settings_navigation';
373 if (function_exists($function)) {
374 $function($settingsnav, $booknode);
381 * Lists all browsable file areas
382 * @param object $course
383 * @param object $cm
384 * @param object $context
385 * @return array
387 function book_get_file_areas($course, $cm, $context) {
388 $areas = array();
389 $areas['chapter'] = get_string('chapters', 'mod_book');
390 return $areas;
394 * File browsing support for book module chapter area.
395 * @param object $browser
396 * @param object $areas
397 * @param object $course
398 * @param object $cm
399 * @param object $context
400 * @param string $filearea
401 * @param int $itemid
402 * @param string $filepath
403 * @param string $filename
404 * @return object file_info instance or null if not found
406 function book_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
407 global $CFG, $DB;
409 // note: 'intro' area is handled in file_browser automatically
411 if (!has_capability('mod/book:read', $context)) {
412 return null;
415 if ($filearea !== 'chapter') {
416 return null;
419 require_once(__DIR__.'/locallib.php');
421 if (is_null($itemid)) {
422 return new book_file_info($browser, $course, $cm, $context, $areas, $filearea);
425 $fs = get_file_storage();
426 $filepath = is_null($filepath) ? '/' : $filepath;
427 $filename = is_null($filename) ? '.' : $filename;
428 if (!$storedfile = $fs->get_file($context->id, 'mod_book', $filearea, $itemid, $filepath, $filename)) {
429 return null;
432 // modifications may be tricky - may cause caching problems
433 $canwrite = has_capability('mod/book:edit', $context);
435 $chaptername = $DB->get_field('book_chapters', 'title', array('bookid'=>$cm->instance, 'id'=>$itemid));
436 $chaptername = format_string($chaptername, true, array('context'=>$context));
438 $urlbase = $CFG->wwwroot.'/pluginfile.php';
439 return new file_info_stored($browser, $context, $storedfile, $urlbase, $chaptername, true, true, $canwrite, false);
443 * Serves the book attachments. Implements needed access control ;-)
445 * @param stdClass $course course object
446 * @param cm_info $cm course module object
447 * @param context $context context object
448 * @param string $filearea file area
449 * @param array $args extra arguments
450 * @param bool $forcedownload whether or not force download
451 * @param array $options additional options affecting the file serving
452 * @return bool false if file not found, does not return if found - just send the file
454 function book_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
455 global $CFG, $DB;
457 if ($context->contextlevel != CONTEXT_MODULE) {
458 return false;
461 require_course_login($course, true, $cm);
463 if ($filearea !== 'chapter') {
464 return false;
467 if (!has_capability('mod/book:read', $context)) {
468 return false;
471 $chid = (int)array_shift($args);
473 if (!$book = $DB->get_record('book', array('id'=>$cm->instance))) {
474 return false;
477 if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chid, 'bookid'=>$book->id))) {
478 return false;
481 if ($chapter->hidden and !has_capability('mod/book:viewhiddenchapters', $context)) {
482 return false;
485 // Download the contents of a chapter as an html file.
486 if ($args[0] == 'index.html') {
487 $filename = "index.html";
489 // We need to rewrite the pluginfile URLs so the media filters can work.
490 $content = file_rewrite_pluginfile_urls($chapter->content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
491 $chapter->id);
492 $formatoptions = new stdClass;
493 $formatoptions->noclean = true;
494 $formatoptions->overflowdiv = true;
495 $formatoptions->context = $context;
497 $content = format_text($content, $chapter->contentformat, $formatoptions);
499 // Remove @@PLUGINFILE@@/.
500 $options = array('reverse' => true);
501 $content = file_rewrite_pluginfile_urls($content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
502 $chapter->id, $options);
503 $content = str_replace('@@PLUGINFILE@@/', '', $content);
505 $titles = "";
506 // Format the chapter titles.
507 if (!$book->customtitles) {
508 require_once(__DIR__.'/locallib.php');
509 $chapters = book_preload_chapters($book);
511 if (!$chapter->subchapter) {
512 $currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
513 // Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
514 $titles = "<h3>$currtitle</h3>";
515 } else {
516 $currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
517 $currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
518 // Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
519 $titles = "<h3>$currtitle</h3>";
520 $titles .= "<h4>$currsubtitle</h4>";
524 $content = $titles . $content;
526 send_file($content, $filename, 0, 0, true, true);
527 } else {
528 $fs = get_file_storage();
529 $relativepath = implode('/', $args);
530 $fullpath = "/$context->id/mod_book/chapter/$chid/$relativepath";
531 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
532 return false;
535 // Nasty hack because we do not have file revisions in book yet.
536 $lifetime = $CFG->filelifetime;
537 if ($lifetime > 60 * 10) {
538 $lifetime = 60 * 10;
541 // Finally send the file.
542 send_stored_file($file, $lifetime, 0, $forcedownload, $options);
547 * Return a list of page types
549 * @param string $pagetype current page type
550 * @param stdClass $parentcontext Block's parent context
551 * @param stdClass $currentcontext Current context of block
552 * @return array
554 function book_page_type_list($pagetype, $parentcontext, $currentcontext) {
555 $module_pagetype = array('mod-book-*'=>get_string('page-mod-book-x', 'mod_book'));
556 return $module_pagetype;
560 * Export book resource contents
562 * @param stdClass $cm Course module object
563 * @param string $baseurl Base URL for file downloads
564 * @return array of file content
566 function book_export_contents($cm, $baseurl) {
567 global $DB;
569 $contents = array();
570 $context = context_module::instance($cm->id);
572 $book = $DB->get_record('book', array('id' => $cm->instance), '*', MUST_EXIST);
574 $fs = get_file_storage();
576 $chapters = $DB->get_records('book_chapters', array('bookid' => $book->id), 'pagenum');
578 $structure = array();
579 $currentchapter = 0;
581 foreach ($chapters as $chapter) {
582 if ($chapter->hidden) {
583 continue;
586 // Generate the book structure.
587 $thischapter = array(
588 "title" => format_string($chapter->title, true, array('context' => $context)),
589 "href" => $chapter->id . "/index.html",
590 "level" => 0,
591 "subitems" => array()
594 // Main chapter.
595 if (!$chapter->subchapter) {
596 $currentchapter = $chapter->pagenum;
597 $structure[$currentchapter] = $thischapter;
598 } else {
599 // Subchapter.
600 $thischapter['level'] = 1;
601 $structure[$currentchapter]["subitems"][] = $thischapter;
604 // Export the chapter contents.
606 // Main content (html).
607 $filename = 'index.html';
608 $chapterindexfile = array();
609 $chapterindexfile['type'] = 'file';
610 $chapterindexfile['filename'] = $filename;
611 // Each chapter in a subdirectory.
612 $chapterindexfile['filepath'] = "/{$chapter->id}/";
613 $chapterindexfile['filesize'] = 0;
614 $chapterindexfile['fileurl'] = moodle_url::make_webservice_pluginfile_url(
615 $context->id, 'mod_book', 'chapter', $chapter->id, '/', 'index.html')->out(false);
616 $chapterindexfile['timecreated'] = $chapter->timecreated;
617 $chapterindexfile['timemodified'] = $chapter->timemodified;
618 $chapterindexfile['content'] = format_string($chapter->title, true, array('context' => $context));
619 $chapterindexfile['sortorder'] = 0;
620 $chapterindexfile['userid'] = null;
621 $chapterindexfile['author'] = null;
622 $chapterindexfile['license'] = null;
623 $contents[] = $chapterindexfile;
625 // Chapter files (images usually).
626 $files = $fs->get_area_files($context->id, 'mod_book', 'chapter', $chapter->id, 'sortorder DESC, id ASC', false);
627 foreach ($files as $fileinfo) {
628 $file = array();
629 $file['type'] = 'file';
630 $file['filename'] = $fileinfo->get_filename();
631 $file['filepath'] = "/{$chapter->id}" . $fileinfo->get_filepath();
632 $file['filesize'] = $fileinfo->get_filesize();
633 $file['fileurl'] = moodle_url::make_webservice_pluginfile_url(
634 $context->id, 'mod_book', 'chapter', $chapter->id,
635 $fileinfo->get_filepath(), $fileinfo->get_filename())->out(false);
636 $file['timecreated'] = $fileinfo->get_timecreated();
637 $file['timemodified'] = $fileinfo->get_timemodified();
638 $file['sortorder'] = $fileinfo->get_sortorder();
639 $file['userid'] = $fileinfo->get_userid();
640 $file['author'] = $fileinfo->get_author();
641 $file['license'] = $fileinfo->get_license();
642 $file['mimetype'] = $fileinfo->get_mimetype();
643 $file['isexternalfile'] = $fileinfo->is_external_file();
644 if ($file['isexternalfile']) {
645 $file['repositorytype'] = $fileinfo->get_repository_type();
647 $contents[] = $file;
651 // First content is the structure in encoded JSON format.
652 $structurefile = array();
653 $structurefile['type'] = 'content';
654 $structurefile['filename'] = 'structure';
655 $structurefile['filepath'] = "/";
656 $structurefile['filesize'] = 0;
657 $structurefile['fileurl'] = null;
658 $structurefile['timecreated'] = $book->timecreated;
659 $structurefile['timemodified'] = $book->timemodified;
660 $structurefile['content'] = json_encode(array_values($structure));
661 $structurefile['sortorder'] = 0;
662 $structurefile['userid'] = null;
663 $structurefile['author'] = null;
664 $structurefile['license'] = null;
666 // Add it as first element.
667 array_unshift($contents, $structurefile);
669 return $contents;
673 * Mark the activity completed (if required) and trigger the course_module_viewed event.
675 * @param stdClass $book book object
676 * @param stdClass $chapter chapter object
677 * @param bool $islaschapter is the las chapter of the book?
678 * @param stdClass $course course object
679 * @param stdClass $cm course module object
680 * @param stdClass $context context object
681 * @since Moodle 3.0
683 function book_view($book, $chapter, $islastchapter, $course, $cm, $context) {
685 // First case, we are just opening the book.
686 if (empty($chapter)) {
687 \mod_book\event\course_module_viewed::create_from_book($book, $context)->trigger();
689 } else {
690 \mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter)->trigger();
692 if ($islastchapter) {
693 // We cheat a bit here in assuming that viewing the last page means the user viewed the whole book.
694 $completion = new completion_info($course);
695 $completion->set_module_viewed($cm);
701 * Check if the module has any update that affects the current user since a given time.
703 * @param cm_info $cm course module data
704 * @param int $from the time to check updates from
705 * @param array $filter if we need to check only specific updates
706 * @return stdClass an object with the different type of areas indicating if they were updated or not
707 * @since Moodle 3.2
709 function book_check_updates_since(cm_info $cm, $from, $filter = array()) {
710 global $DB;
712 $context = $cm->context;
713 $updates = new stdClass();
714 if (!has_capability('mod/book:read', $context)) {
715 return $updates;
717 $updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
719 $select = 'bookid = :id AND (timecreated > :since1 OR timemodified > :since2)';
720 $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
721 if (!has_capability('mod/book:viewhiddenchapters', $context)) {
722 $select .= ' AND hidden = 0';
724 $updates->entries = (object) array('updated' => false);
725 $entries = $DB->get_records_select('book_chapters', $select, $params, '', 'id');
726 if (!empty($entries)) {
727 $updates->entries->updated = true;
728 $updates->entries->itemids = array_keys($entries);
731 return $updates;
735 * Get icon mapping for font-awesome.
737 function mod_book_get_fontawesome_icon_map() {
738 return [
739 'mod_book:chapter' => 'fa-bookmark-o',
740 'mod_book:nav_prev' => 'fa-arrow-left',
741 'mod_book:nav_prev_dis' => 'fa-angle-left',
742 'mod_book:nav_sep' => 'fa-minus',
743 'mod_book:add' => 'fa-plus',
744 'mod_book:nav_next' => 'fa-arrow-right',
745 'mod_book:nav_next_dis' => 'fa-angle-right',
746 'mod_book:nav_exit' => 'fa-arrow-up',
751 * This function receives a calendar event and returns the action associated with it, or null if there is none.
753 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
754 * is not displayed on the block.
756 * @param calendar_event $event
757 * @param \core_calendar\action_factory $factory
758 * @return \core_calendar\local\event\entities\action_interface|null
760 function mod_book_core_calendar_provide_event_action(calendar_event $event,
761 \core_calendar\action_factory $factory) {
762 $cm = get_fast_modinfo($event->courseid)->instances['book'][$event->instance];
763 $context = context_module::instance($cm->id);
765 if (!has_capability('mod/book:read', $context)) {
766 return null;
769 $completion = new \completion_info($cm->get_course());
771 $completiondata = $completion->get_data($cm, false);
773 if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
774 return null;
777 return $factory->create_instance(
778 get_string('view'),
779 new \moodle_url('/mod/book/view.php', ['id' => $cm->id]),
781 true