first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
Book module for Moodle (http://moodle.org/) - Copyright (C) 2004-2011 Petr Skoda (http://skodak.org/)
The Book module makes it easy to create multi-page resources with a book-like format. This module can be used to build complete book-like websites inside of your Moodle course.
This module was developed for Technical University of Liberec (Czech Republic). Many ideas and code were taken from other Moodle modules and Moodle itself
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details: http://www.gnu.org/copyleft/gpl.html
Created by:
* Petr Skoda (skodak) - most of the coding & design
* Mojmir Volf, Eloy Lafuente, Antonio Vicent and others
Intentionally omitted features:
* more chapter levels - it would encourage teachers to write too much complex and long books, better use standard standalone HTML editor and import it as Resource. DocBook format is another suitable solution.
* TOC hiding in normal view - instead use printer friendly view
* PDF export - there is no elegant way AFAIK to convert HTML to PDF, use virtual PDF printer or better use DocBook format for authoring
* detailed student tracking (postponed till officially supported)
* export as zipped set of HTML pages - instead use browser command Save page as... in print view
+166
View File
@@ -0,0 +1,166 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides support for the conversion of moodle1 backup to the moodle2 format
*
* @package mod_book
* @copyright 2011 Tõnis Tartes <t6nis20@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Book conversion handler
*/
class moodle1_mod_book_handler extends moodle1_mod_handler {
/** @var moodle1_file_manager */
protected $fileman = null;
/** @var int cmid */
protected $moduleid = null;
/**
* Declare the paths in moodle.xml we are able to convert
*
* The method returns list of {@link convert_path} instances. For each path returned,
* at least one of on_xxx_start(), process_xxx() and on_xxx_end() methods must be
* defined. The method process_xxx() is not executed if the associated path element is
* empty (i.e. it contains none elements or sub-paths only).
*
* Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK does not
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
* @return array of {@link convert_path} instances
*/
public function get_paths() {
return array(
new convert_path('book', '/MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK',
array(
'renamefields' => array(
'summary' => 'intro',
),
'newfields' => array(
'introformat' => FORMAT_MOODLE,
),
'dropfields' => array(
'disableprinting'
),
)
),
new convert_path('book_chapters', '/MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK/CHAPTERS/CHAPTER',
array(
'newfields' => array(
'contentformat' => FORMAT_HTML,
),
)
),
);
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK
* data available
* @param array $data
*/
public function process_book($data) {
global $CFG;
// get the course module id and context id
$instanceid = $data['id'];
$cminfo = $this->get_cminfo($instanceid);
$this->moduleid = $cminfo['id'];
$contextid = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid);
// replay the upgrade step 2009042006
if ($CFG->texteditors !== 'textarea') {
$data['intro'] = text_to_html($data['intro'], false, false, true);
$data['introformat'] = FORMAT_HTML;
}
// get a fresh new file manager for this instance
$this->fileman = $this->converter->get_file_manager($contextid, 'mod_book');
// convert course files embedded into the intro
$this->fileman->filearea = 'intro';
$this->fileman->itemid = 0;
$data['intro'] = moodle1_converter::migrate_referenced_files($data['intro'], $this->fileman);
// start writing book.xml
$this->open_xml_writer("activities/book_{$this->moduleid}/book.xml");
$this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $this->moduleid,
'modulename' => 'book', 'contextid' => $contextid));
$this->xmlwriter->begin_tag('book', array('id' => $instanceid));
foreach ($data as $field => $value) {
if ($field <> 'id') {
$this->xmlwriter->full_tag($field, $value);
}
}
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK/CHAPTERS/CHAPTER
* data available
* @param array $data
*/
public function process_book_chapters($data) {
// Convert chapter files.
$this->fileman->filearea = 'chapter';
$this->fileman->itemid = $data['id'];
$data['content'] = moodle1_converter::migrate_referenced_files($data['content'], $this->fileman);
$this->write_xml('chapter', $data, array('/chapter/id'));
}
/**
* This is executed when the parser reaches the <CHAPTERS> opening element
*/
public function on_book_chapters_start() {
$this->xmlwriter->begin_tag('chapters');
}
/**
* This is executed when the parser reaches the closing </CHAPTERS> element
*/
public function on_book_chapters_end() {
$this->xmlwriter->end_tag('chapters');
}
/**
* This is executed when we reach the closing </MOD> tag of our 'book' path
*/
public function on_book_end() {
// finalize book.xml
$this->xmlwriter->end_tag('book');
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
// write inforef.xml
$this->open_xml_writer("activities/book_{$this->moduleid}/inforef.xml");
$this->xmlwriter->begin_tag('inforef');
$this->xmlwriter->begin_tag('fileref');
foreach ($this->fileman->get_fileids() as $fileid) {
$this->write_xml('file', array('id' => $fileid));
}
$this->xmlwriter->end_tag('fileref');
$this->xmlwriter->end_tag('inforef');
$this->close_xml_writer();
}
}
@@ -0,0 +1,83 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Description of book backup task
*
* @package mod_book
* @copyright 2010-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/mod/book/backup/moodle2/backup_book_stepslib.php'); // Because it exists (must)
require_once($CFG->dirroot.'/mod/book/backup/moodle2/backup_book_settingslib.php'); // Because it exists (optional)
class backup_book_activity_task extends backup_activity_task {
/**
* Define (add) particular settings this activity can have
*
* @return void
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*
* @return void
*/
protected function define_my_steps() {
// book only has one structure step
$this->add_step(new backup_book_activity_structure_step('book_structure', 'book.xml'));
}
/**
* Code the transformations to perform in the activity in
* order to get transportable (encoded) links
*
* @param string $content
* @return string encoded content
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot, "/");
// Link to the list of books
$search = "/($base\/mod\/book\/index.php\?id=)([0-9]+)/";
$content = preg_replace($search, '$@BOOKINDEX*$2@$', $content);
// Link to book view by moduleid
$search = "/($base\/mod\/book\/view.php\?id=)([0-9]+)(&|&amp;)chapterid=([0-9]+)/";
$content = preg_replace($search, '$@BOOKVIEWBYIDCH*$2*$4@$', $content);
$search = "/($base\/mod\/book\/view.php\?id=)([0-9]+)/";
$content = preg_replace($search, '$@BOOKVIEWBYID*$2@$', $content);
// Link to book view by bookid
$search = "/($base\/mod\/book\/view.php\?b=)([0-9]+)(&|&amp;)chapterid=([0-9]+)/";
$content = preg_replace($search, '$@BOOKVIEWBYBCH*$2*$4@$', $content);
$search = "/($base\/mod\/book\/view.php\?b=)([0-9]+)/";
$content = preg_replace($search, '$@BOOKVIEWBYB*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Description of book backup settings
*
* @package mod_book
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
// This activity has not particular settings but the inherited from the generic
// backup_activity_task so here there isn't any class definition, like the ones
// existing in /backup/moodle2/backup_settingslib.php (activities section)
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Define all the backup steps that will be used by the backup_book_activity_task
*
* @package mod_book
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Structure step to backup one book activity
*/
class backup_book_activity_structure_step extends backup_activity_structure_step {
protected function define_structure() {
// Define each element separated.
$book = new backup_nested_element('book', array('id'), array(
'name', 'intro', 'introformat', 'numbering', 'navstyle',
'customtitles', 'timecreated', 'timemodified'));
$chapters = new backup_nested_element('chapters');
$chapter = new backup_nested_element('chapter', array('id'), array(
'pagenum', 'subchapter', 'title', 'content', 'contentformat',
'hidden', 'timemcreated', 'timemodified', 'importsrc'));
$tags = new backup_nested_element('chaptertags');
$tag = new backup_nested_element('tag', array('id'), array('itemid', 'rawname'));
$book->add_child($chapters);
$chapters->add_child($chapter);
// Define sources
$book->set_source_table('book', array('id' => backup::VAR_ACTIVITYID));
$chapter->set_source_table('book_chapters', array('bookid' => backup::VAR_PARENTID));
// Define file annotations
$book->annotate_files('mod_book', 'intro', null); // This file area hasn't itemid
$chapter->annotate_files('mod_book', 'chapter', 'id');
$book->add_child($tags);
$tags->add_child($tag);
// All these source definitions only happen if we are including user info.
if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) {
$tag->set_source_sql('SELECT t.id, ti.itemid, t.rawname
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemtype = ?
AND ti.component = ?
AND ti.contextid = ?', array(
backup_helper::is_sqlparam('book_chapters'),
backup_helper::is_sqlparam('mod_book'),
backup::VAR_CONTEXTID));
}
// Return the root element (book), wrapped into standard activity structure
return $this->prepare_activity_structure($book);
}
}
@@ -0,0 +1,141 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Description of book restore task
*
* @package mod_book
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/book/backup/moodle2/restore_book_stepslib.php'); // Because it exists (must)
class restore_book_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have
*
* @return void
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*
* @return void
*/
protected function define_my_steps() {
// Choice only has one structure step
$this->add_step(new restore_book_activity_structure_step('book_structure', 'book.xml'));
}
/**
* Define the contents in the activity that must be
* processed by the link decoder
*
* @return array
*/
public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('book', array('intro'), 'book');
$contents[] = new restore_decode_content('book_chapters', array('content'), 'book_chapter');
return $contents;
}
/**
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*
* @return array
*/
public static function define_decode_rules() {
$rules = array();
// List of books in course
$rules[] = new restore_decode_rule('BOOKINDEX', '/mod/book/index.php?id=$1', 'course');
// book by cm->id
$rules[] = new restore_decode_rule('BOOKVIEWBYID', '/mod/book/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('BOOKVIEWBYIDCH', '/mod/book/view.php?id=$1&amp;chapterid=$2', array('course_module', 'book_chapter'));
// book by book->id
$rules[] = new restore_decode_rule('BOOKVIEWBYB', '/mod/book/view.php?b=$1', 'book');
$rules[] = new restore_decode_rule('BOOKVIEWBYBCH', '/mod/book/view.php?b=$1&amp;chapterid=$2', array('book', 'book_chapter'));
// Convert old book links MDL-33362 & MDL-35007
$rules[] = new restore_decode_rule('BOOKSTART', '/mod/book/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('BOOKCHAPTER', '/mod/book/view.php?id=$1&amp;chapterid=$2', array('course_module', 'book_chapter'));
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* book logs. It must return one array
* of {@link restore_log_rule} objects
*
* @return array
*/
public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('book', 'add', 'view.php?id={course_module}', '{book}');
$rules[] = new restore_log_rule('book', 'update', 'view.php?id={course_module}&chapterid={book_chapter}', '{book}');
$rules[] = new restore_log_rule('book', 'update', 'view.php?id={course_module}', '{book}');
$rules[] = new restore_log_rule('book', 'view', 'view.php?id={course_module}&chapterid={book_chapter}', '{book}');
$rules[] = new restore_log_rule('book', 'view', 'view.php?id={course_module}', '{book}');
$rules[] = new restore_log_rule('book', 'print', 'tool/print/index.php?id={course_module}&chapterid={book_chapter}', '{book}');
$rules[] = new restore_log_rule('book', 'print', 'tool/print/index.php?id={course_module}', '{book}');
$rules[] = new restore_log_rule('book', 'exportimscp', 'tool/exportimscp/index.php?id={course_module}', '{book}');
// To convert old 'generateimscp' log entries
$rules[] = new restore_log_rule('book', 'generateimscp', 'tool/generateimscp/index.php?id={course_module}', '{book}',
'book', 'exportimscp', 'tool/exportimscp/index.php?id={course_module}', '{book}');
$rules[] = new restore_log_rule('book', 'print chapter', 'tool/print/index.php?id={course_module}&chapterid={book_chapter}', '{book_chapter}');
$rules[] = new restore_log_rule('book', 'update chapter', 'view.php?id={course_module}&chapterid={book_chapter}', '{book_chapter}');
$rules[] = new restore_log_rule('book', 'add chapter', 'view.php?id={course_module}&chapterid={book_chapter}', '{book_chapter}');
$rules[] = new restore_log_rule('book', 'view chapter', 'view.php?id={course_module}&chapterid={book_chapter}', '{book_chapter}');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*
* @return array
*/
public static function define_restore_log_rules_for_course() {
$rules = array();
$rules[] = new restore_log_rule('book', 'view all', 'index.php?id={course}', null);
return $rules;
}
}
@@ -0,0 +1,102 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Define all the restore steps that will be used by the restore_book_activity_task
*
* @package mod_book
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Structure step to restore one book activity
*/
class restore_book_activity_structure_step extends restore_activity_structure_step {
protected function define_structure() {
$paths = array();
$paths[] = new restore_path_element('book', '/activity/book');
$paths[] = new restore_path_element('book_chapter', '/activity/book/chapters/chapter');
$paths[] = new restore_path_element('book_chapter_tag', '/activity/book/chaptertags/tag');
// Return the paths wrapped into standard activity structure
return $this->prepare_activity_structure($paths);
}
/**
* Process book tag information
* @param array $data information
*/
protected function process_book($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
$newitemid = $DB->insert_record('book', $data);
$this->apply_activity_instance($newitemid);
}
/**
* Process chapter tag information
* @param array $data information
*/
protected function process_book_chapter($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
$data->bookid = $this->get_new_parentid('book');
$newitemid = $DB->insert_record('book_chapters', $data);
$this->set_mapping('book_chapter', $oldid, $newitemid, true);
}
protected function process_book_chapter_tag($data) {
$data = (object)$data;
if (!core_tag_tag::is_enabled('mod_book', 'book_chapters')) { // Tags disabled in server, nothing to process.
return;
}
$tag = $data->rawname;
if (!$itemid = $this->get_mappingid('book_chapter', $data->itemid)) {
return;
}
$context = context_module::instance($this->task->get_moduleid());
core_tag_tag::add_item_tag('mod_book', 'book_chapters', $itemid, $context, $tag);
}
protected function after_execute() {
global $DB;
// Add book related files
$this->add_related_files('mod_book', 'intro', null);
$this->add_related_files('mod_book', 'chapter', 'book_chapter');
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Activity base class.
*
* @package mod_book
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_book
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* No need to fetch grades for resources.
*
* @param \core_analytics\course $course
* @return void
*/
public function fetch_student_grades(\core_analytics\course $course) {
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cognitive depth indicator - book.
*
* @package mod_book
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - book.
*
* @package mod_book
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_book');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_1;
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Social breadth indicator - book.
*
* @package mod_book
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - book.
*
* @package mod_book
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_book');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_1;
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book chapter created event.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book chapter created event class.
*
* @package mod_book
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class chapter_created extends \core\event\base {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @param \stdClass $chapter
* @return chapter_created
*/
public static function create_from_chapter(\stdClass $book, \context_module $context, \stdClass $chapter) {
$data = array(
'context' => $context,
'objectid' => $chapter->id,
);
/** @var chapter_created $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
$event->add_record_snapshot('book_chapters', $chapter);
return $event;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the chapter with id '$this->objectid' for the book with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventchaptercreated', 'mod_book');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/book/view.php', array(
'id' => $this->contextinstanceid,
'chapterid' => $this->objectid
));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'book_chapters';
}
public static function get_objectid_mapping() {
return array('db' => 'book_chapters', 'restore' => 'book_chapter');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book chapter deleted event.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book chapter deleted event class.
*
* @package mod_book
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class chapter_deleted extends \core\event\base {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @param \stdClass $chapter
* @return chapter_deleted
*/
public static function create_from_chapter(\stdClass $book, \context_module $context, \stdClass $chapter) {
$data = array(
'context' => $context,
'objectid' => $chapter->id,
);
/** @var chapter_deleted $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
$event->add_record_snapshot('book_chapters', $chapter);
return $event;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the chapter with id '$this->objectid' for the book with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventchapterdeleted', 'mod_book');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/book/view.php', array('id' => $this->contextinstanceid));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'book_chapters';
}
public static function get_objectid_mapping() {
return array('db' => 'book_chapters', 'restore' => 'book_chapter');
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book chapter updated event.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book chapter updated event class.
*
* @package mod_book
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class chapter_updated extends \core\event\base {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @param \stdClass $chapter
* @return chapter_updated
*/
public static function create_from_chapter(\stdClass $book, \context_module $context, \stdClass $chapter) {
$data = array(
'context' => $context,
'objectid' => $chapter->id,
);
/** @var chapter_updated $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
$event->add_record_snapshot('book_chapters', $chapter);
return $event;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the chapter with id '$this->objectid' for the book with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventchapterupdated', 'mod_book');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/book/view.php', array(
'id' => $this->contextinstanceid,
'chapterid' => $this->objectid
));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'book_chapters';
}
public static function get_objectid_mapping() {
return array('db' => 'book_chapters', 'restore' => 'book_chapter');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book chapter viewed event.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book chapter viewed event class.
*
* @package mod_book
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class chapter_viewed extends \core\event\base {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @param \stdClass $chapter
* @return chapter_viewed
*/
public static function create_from_chapter(\stdClass $book, \context_module $context, \stdClass $chapter) {
$data = array(
'context' => $context,
'objectid' => $chapter->id,
);
/** @var chapter_viewed $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
$event->add_record_snapshot('book_chapters', $chapter);
return $event;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the chapter with id '$this->objectid' for the book with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventchapterviewed', 'mod_book');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/book/view.php', array('id' => $this->contextinstanceid, 'chapterid' => $this->objectid));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'book_chapters';
}
public static function get_objectid_mapping() {
return array('db' => 'book_chapters', 'restore' => 'book_chapter');
}
}
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book instance list viewed event.
*
* @package mod_book
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book instance list viewed event class.
*
* @package mod_book
* @since Moodle 2.7
* @copyright 2013 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
/**
* Create the event from course record.
*
* @param \stdClass $course
* @return course_module_instance_list_viewed
*/
public static function create_from_course(\stdClass $course) {
$params = array(
'context' => \context_course::instance($course->id)
);
$event = \mod_book\event\course_module_instance_list_viewed::create($params);
$event->add_record_snapshot('course', $course);
return $event;
}}
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_book course module viewed event.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book course module viewed event class.
*
* @package mod_book
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @return course_module_viewed
*/
public static function create_from_book(\stdClass $book, \context_module $context) {
$data = array(
'context' => $context,
'objectid' => $book->id
);
/** @var course_module_viewed $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
return $event;
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'book';
}
public static function get_objectid_mapping() {
return array('db' => 'book', 'restore' => 'book');
}
}
+246
View File
@@ -0,0 +1,246 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book external API
*
* @package mod_book
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
use core_course\external\helper_for_get_mods_by_courses;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util;
/**
* Book external functions
*
* @package mod_book
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class mod_book_external extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function view_book_parameters() {
return new external_function_parameters(
array(
'bookid' => new external_value(PARAM_INT, 'book instance id'),
'chapterid' => new external_value(PARAM_INT, 'chapter id', VALUE_DEFAULT, 0)
)
);
}
/**
* Simulate the book/view.php web interface page: trigger events, completion, etc...
*
* @param int $bookid the book instance id
* @param int $chapterid the book chapter id
* @return array of warnings and status result
* @since Moodle 3.0
* @throws moodle_exception
*/
public static function view_book($bookid, $chapterid = 0) {
global $DB, $CFG;
require_once($CFG->dirroot . "/mod/book/lib.php");
require_once($CFG->dirroot . "/mod/book/locallib.php");
$params = self::validate_parameters(self::view_book_parameters(),
array(
'bookid' => $bookid,
'chapterid' => $chapterid
));
$bookid = $params['bookid'];
$chapterid = $params['chapterid'];
$warnings = array();
// Request and permission validation.
$book = $DB->get_record('book', array('id' => $bookid), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($book, 'book');
$context = context_module::instance($cm->id);
self::validate_context($context);
require_capability('mod/book:read', $context);
$chapters = book_preload_chapters($book);
$firstchapterid = 0;
foreach ($chapters as $ch) {
if ($ch->hidden) {
continue;
}
if (!$firstchapterid) {
$firstchapterid = $ch->id;
}
}
if (!$chapterid) {
// Trigger the module viewed events since we are displaying the book.
book_view($book, null, false, $course, $cm, $context);
$chapterid = $firstchapterid;
}
// Check if book is empty (warning).
if (!$chapterid) {
$warnings[] = array(
'item' => 'book',
'itemid' => $book->id,
'warningcode' => '1',
'message' => get_string('nocontent', 'mod_book')
);
} else {
$chapter = $DB->get_record('book_chapters', array('id' => $chapterid, 'bookid' => $book->id));
$viewhidden = has_capability('mod/book:viewhiddenchapters', $context);
if (!$chapter or ($chapter->hidden and !$viewhidden)) {
throw new moodle_exception('errorchapter', 'mod_book');
}
// Trigger the chapter viewed event.
book_view($book, $chapter, \mod_book\helper::is_last_visible_chapter($chapterid, $chapters), $course, $cm, $context);
}
$result = array();
$result['status'] = true;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function view_book_returns() {
return new external_single_structure(
array(
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
)
);
}
/**
* Describes the parameters for get_books_by_courses.
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function get_books_by_courses_parameters() {
return new external_function_parameters (
array(
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
),
)
);
}
/**
* Returns a list of books in a provided list of courses,
* if no list is provided all books that the user can view will be returned.
*
* @param array $courseids the course ids
* @return array of books details
* @since Moodle 3.0
*/
public static function get_books_by_courses($courseids = array()) {
$returnedbooks = array();
$warnings = array();
$params = self::validate_parameters(self::get_books_by_courses_parameters(), array('courseids' => $courseids));
$courses = array();
if (empty($params['courseids'])) {
$courses = enrol_get_my_courses();
$params['courseids'] = array_keys($courses);
}
// Ensure there are courseids to loop through.
if (!empty($params['courseids'])) {
list($courses, $warnings) = util::validate_courses($params['courseids'], $courses);
// Get the books in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$books = get_all_instances_in_courses("book", $courses);
foreach ($books as $book) {
// Entry to return.
$bookdetails = helper_for_get_mods_by_courses::standard_coursemodule_element_values($book, 'mod_book');
$bookdetails['numbering'] = $book->numbering;
$bookdetails['navstyle'] = $book->navstyle;
$bookdetails['customtitles'] = $book->customtitles;
if (has_capability('moodle/course:manageactivities', context_module::instance($book->coursemodule))) {
$bookdetails['revision'] = $book->revision;
$bookdetails['timecreated'] = $book->timecreated;
$bookdetails['timemodified'] = $book->timemodified;
}
$returnedbooks[] = $bookdetails;
}
}
$result = array();
$result['books'] = $returnedbooks;
$result['warnings'] = $warnings;
return $result;
}
/**
* Describes the get_books_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function get_books_by_courses_returns() {
return new external_single_structure(
array(
'books' => new external_multiple_structure(
new external_single_structure(array_merge(
helper_for_get_mods_by_courses::standard_coursemodule_elements_returns(),
[
'numbering' => new external_value(PARAM_INT, 'Book numbering configuration'),
'navstyle' => new external_value(PARAM_INT, 'Book navigation style configuration'),
'customtitles' => new external_value(PARAM_INT, 'Book custom titles type'),
'revision' => new external_value(PARAM_INT, 'Book revision', VALUE_OPTIONAL),
'timecreated' => new external_value(PARAM_INT, 'Time of creation', VALUE_OPTIONAL),
'timemodified' => new external_value(PARAM_INT, 'Time of last modification', VALUE_OPTIONAL),
]
), 'Books')
),
'warnings' => new external_warnings(),
)
);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_book;
/**
* Book helper
*
* @package mod_book
* @copyright 2023 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Check if we are on the last visible chapter of the book.
*
* @param int $chapterid
* @param array $chapters chapter list provided by book_preload_chapters
* @see book_preload_chapters
* @return bool
*/
public static function is_last_visible_chapter(int $chapterid, array $chapters): bool {
$lastchapterid = 0;
foreach ($chapters as $ch) {
if ($ch->hidden) {
continue;
}
$lastchapterid = $ch->id;
}
return $chapterid == $lastchapterid;
}
}
@@ -0,0 +1,153 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Output the action menu for this activity.
*
* @package mod_book
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\output;
use templatable;
use renderable;
use moodle_url;
use stdClass;
/**
* Output the action menu for the book activity.
*
* @package mod_book
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class main_action_menu implements templatable, renderable {
/** @var int The course module ID. */
protected $cmid;
/** @var stdClass[] Chapters of the book. */
protected $chapters;
/** @var stdClass Current chapter of the book. */
protected $chapter;
/**
* Constructor for this class.
*
* @param int $cmid The course module ID.
* @param array $chapters Chapters of this book.
* @param stdClass $chapter The current chapter.
*/
public function __construct(int $cmid, array $chapters, stdClass $chapter) {
$this->cmid = $cmid;
$this->chapters = $chapters;
$this->chapter = $chapter;
}
/**
* Get the next chapter in the book.
*
* @return ?stdClass The next chapter of the book.
*/
protected function get_next_chapter(): ?stdClass {
$nextpageid = $this->chapter->pagenum + 1;
// Early return if the current chapter is also the last chapter.
if ($nextpageid > count($this->chapters)) {
return null;
}
while ((!$nextchapter = $this->get_chapter($nextpageid))) {
// Break the loop if this is the last chapter.
if ($nextpageid === count($this->chapters)) {
break;
}
$nextpageid++;
}
return $nextchapter;
}
/**
* Get the previous chapter in the book.
*
* @return ?stdClass The previous chapter of the book.
*/
protected function get_previous_chapter(): ?stdClass {
$prevpageid = $this->chapter->pagenum - 1;
// Early return if the current chapter is also the first chapter.
if ($prevpageid < 1) {
return null;
}
while ((!$prevchapter = $this->get_chapter($prevpageid))) {
// Break the loop if this is the first chapter.
if ($prevpageid === 1) {
break;
}
$prevpageid--;
}
return $prevchapter;
}
/**
* Get the specific chapter of the book.
*
* @param int $id The chapter id to retrieve.
* @return ?stdClass The requested chapter.
*/
protected function get_chapter(int $id): ?stdClass {
$context = \context_module::instance($this->cmid);
$viewhidden = has_capability('mod/book:viewhiddenchapters', $context);
foreach ($this->chapters as $chapter) {
// Also make sure that the chapter is not hidden or the user can view hidden chapters before returning
// the chapter object.
if (($chapter->pagenum == $id) && (!$chapter->hidden || $viewhidden)) {
return $chapter;
}
}
return null;
}
/**
* Exports the navigation buttons around the book.
*
* @param \renderer_base $output renderer base output.
* @return array Data to render.
*/
public function export_for_template(\renderer_base $output): array {
$next = $this->get_next_chapter();
$previous = $this->get_previous_chapter();
$context = \context_module::instance($this->cmid);
$data = [];
if ($next) {
$nextdata = [
'title' => get_string('navnext', 'mod_book'),
'url' => (new moodle_url('/mod/book/view.php', ['id' => $this->cmid, 'chapterid' => $next->id]))->out(false)
];
$data['next'] = $nextdata;
}
if ($previous) {
$previousdata = [
'title' => get_string('navprev', 'mod_book'),
'url' => (new moodle_url('/mod/book/view.php', ['id' => $this->cmid, 'chapterid' => $previous->id]))->out(false)
];
$data['previous'] = $previousdata;
}
return $data;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Moodle renderer used to display special elements of the book module
*
* @package mod_book
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\output;
/**
* Moodle renderer used to display special elements of the book module
*
* @package mod_book
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Renderers the main action menu.
*
* @param main_action_menu $actionmenu Main action menu object.
* @return string The rendered html.
*/
public function render_main_action_menu(main_action_menu $actionmenu): string {
$context = $actionmenu->export_for_template($this);
return $this->render_from_template('mod_book/main_action_menu', $context);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Subplugin info class.
*
* @package mod_book
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class booktool extends base {
public function is_uninstall_allowed() {
return true;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for mod_book.
*
* @package mod_book
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_book module does not store any data.
*
* @package mod_book
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Search area for mod_book activities.
*
* @package mod_book
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_book activities.
*
* @package mod_book
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
}
+190
View File
@@ -0,0 +1,190 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Search area for mod_book chapters.
*
* @package mod_book
* @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_book chapters.
*
* @package mod_book
* @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class chapter extends \core_search\base_mod {
/**
* @var array Cache of book records.
*/
protected $bookscache = array();
/**
* Returns a recordset with all required chapter information.
*
* @param int $modifiedfrom
* @param \context|null $context Optional context to restrict scope of returned results
* @return moodle_recordset|null Recordset (or null if no results)
*/
public function get_document_recordset($modifiedfrom = 0, \context $context = null) {
global $DB;
list ($contextjoin, $contextparams) = $this->get_context_restriction_sql(
$context, 'book', 'b');
if ($contextjoin === null) {
return null;
}
$sql = "SELECT c.*, b.id AS bookid, b.course AS courseid
FROM {book_chapters} c
JOIN {book} b ON b.id = c.bookid
$contextjoin
WHERE c.timemodified >= ? ORDER BY c.timemodified ASC";
return $DB->get_recordset_sql($sql, array_merge($contextparams, [$modifiedfrom]));
}
/**
* Returns the document for a particular chapter.
*
* @param \stdClass $record A record containing, at least, the indexed document id and a modified timestamp
* @param array $options Options for document creation
* @return \core_search\document
*/
public function get_document($record, $options = array()) {
try {
$cm = $this->get_cm('book', $record->bookid, $record->courseid);
$context = \context_module::instance($cm->id);
} catch (\dml_missing_record_exception $ex) {
// Notify it as we run here as admin, we should see everything.
debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document, not all required data is available: ' .
$ex->getMessage(), DEBUG_DEVELOPER);
return false;
} catch (\dml_exception $ex) {
// Notify it as we run here as admin, we should see everything.
debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document: ' . $ex->getMessage(), DEBUG_DEVELOPER);
return false;
}
// Prepare associative array with data from DB.
$doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
$doc->set('title', content_to_text($record->title, false));
$doc->set('content', content_to_text($record->content, $record->contentformat));
$doc->set('contextid', $context->id);
$doc->set('courseid', $record->courseid);
$doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
$doc->set('modified', $record->timemodified);
// Check if this document should be considered new.
if (isset($options['lastindexedtime']) && ($options['lastindexedtime'] < $record->timecreated)) {
// If the document was created after the last index time, it must be new.
$doc->set_is_new(true);
}
return $doc;
}
/**
* Can the current user see the document.
*
* @param int $id The internal search area entity id.
* @return bool True if the user can see it, false otherwise
*/
public function check_access($id) {
global $DB;
try {
$chapter = $DB->get_record('book_chapters', array('id' => $id), '*', MUST_EXIST);
if (!isset($this->bookscache[$chapter->bookid])) {
$this->bookscache[$chapter->bookid] = $DB->get_record('book', array('id' => $chapter->bookid), '*', MUST_EXIST);
}
$book = $this->bookscache[$chapter->bookid];
$cminfo = $this->get_cm('book', $chapter->bookid, $book->course);
} catch (\dml_missing_record_exception $ex) {
return \core_search\manager::ACCESS_DELETED;
} catch (\dml_exception $ex) {
return \core_search\manager::ACCESS_DENIED;
}
// Recheck uservisible although it should have already been checked in core_search.
if ($cminfo->uservisible === false) {
return \core_search\manager::ACCESS_DENIED;
}
$context = \context_module::instance($cminfo->id);
if (!has_capability('mod/book:read', $context)) {
return \core_search\manager::ACCESS_DENIED;
}
// See if the user can see chapter if it is hidden.
if ($chapter->hidden && !has_capability('mod/book:viewhiddenchapters', $context)) {
return \core_search\manager::ACCESS_DENIED;
}
return \core_search\manager::ACCESS_GRANTED;
}
/**
* Returns a url to the chapter.
*
* @param \core_search\document $doc
* @return \moodle_url
*/
public function get_doc_url(\core_search\document $doc) {
$contextmodule = \context::instance_by_id($doc->get('contextid'));
$params = array('id' => $contextmodule->instanceid, 'chapterid' => $doc->get('itemid'));
return new \moodle_url('/mod/book/view.php', $params);
}
/**
* Returns a url to the book.
*
* @param \core_search\document $doc
* @return \moodle_url
*/
public function get_context_url(\core_search\document $doc) {
$contextmodule = \context::instance_by_id($doc->get('contextid'));
return new \moodle_url('/mod/book/view.php', array('id' => $contextmodule->instanceid));
}
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
/**
* Return the context info required to index files for
* this search area.
*
* @return array
*/
public function get_search_fileareas() {
$fileareas = array('chapter'); // Filearea.
return $fileareas;
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module capability definition
*
* @package mod_book
* @copyright 2009-2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'mod/book:addinstance' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
),
'mod/book:read' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'guest' => CAP_ALLOW,
'frontpage' => CAP_ALLOW,
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
),
'mod/book:viewhiddenchapters' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
),
'mod/book:edit' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
),
);
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/book/db" VERSION="20220530" COMMENT="XMLDB file for Moodle mod_book"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="book" COMMENT="Defines book">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="numbering" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="navstyle" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="customtitles" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="revision" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="course" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="book_chapters" COMMENT="Defines book_chapters">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="bookid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="pagenum" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="subchapter" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="title" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="content" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="contentformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="hidden" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="importsrc" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="bookid" UNIQUE="false" FIELDS="bookid"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
+34
View File
@@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module log events definition
*
* @package mod_book
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$logs = array(
array('module' => 'book', 'action' => 'add', 'mtable' => 'book', 'field' => 'name'),
array('module' => 'book', 'action' => 'update', 'mtable' => 'book', 'field' => 'name'),
array('module' => 'book', 'action' => 'view', 'mtable' => 'book', 'field' => 'name'),
array('module' => 'book', 'action' => 'add chapter', 'mtable' => 'book_chapters', 'field' => 'title'),
array('module' => 'book', 'action' => 'update chapter', 'mtable'=> 'book_chapters', 'field' => 'title'),
array('module' => 'book', 'action' => 'view chapter', 'mtable' => 'book_chapters', 'field' => 'title')
);
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book external functions and service definitions.
*
* @package mod_book
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
defined('MOODLE_INTERNAL') || die;
$functions = array(
'mod_book_view_book' => array(
'classname' => 'mod_book_external',
'methodname' => 'view_book',
'description' => 'Simulate the view.php web interface book: trigger events, completion, etc...',
'type' => 'write',
'capabilities' => 'mod/book:read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_book_get_books_by_courses' => array(
'classname' => 'mod_book_external',
'methodname' => 'get_books_by_courses',
'description' => 'Returns a list of book instances in a provided set of courses,
if no courses are provided then all the book instances the user has access to will be returned.',
'type' => 'read',
'capabilities' => '',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
)
);
+5
View File
@@ -0,0 +1,5 @@
{
"plugintypes": {
"booktool": "mod\/book\/tool"
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag areas in component mod_book
*
* @package mod_book
* @copyright 2017 Andrew Hancox <andrewdchancox@googlemail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tagareas = array(
array(
'itemtype' => 'book_chapters',
'component' => 'mod_book',
'callback' => 'mod_book_get_tagged_chapters',
'callbackfile' => '/mod/book/locallib.php',
),
);
+44
View File
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module upgrade code
*
* @package mod_book
* @copyright 2009-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Book module upgrade task
*
* @param int $oldversion the version we are upgrading from
* @return bool always true
*/
function xmldb_book_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+109
View File
@@ -0,0 +1,109 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Delete book chapter
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
// Course Module ID.
$id = required_param('id', PARAM_INT);
// Chapter ID.
$chapterid = required_param('chapterid', PARAM_INT);
$confirm = optional_param('confirm', 0, PARAM_BOOL);
$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$book = $DB->get_record('book', ['id' => $cm->instance], '*', MUST_EXIST);
$chapter = $DB->get_record('book_chapters', ['id' => $chapterid, 'bookid' => $book->id], '*', MUST_EXIST);
require_login($course, false, $cm);
require_sesskey();
$context = context_module::instance($cm->id);
require_capability('mod/book:edit', $context);
$PAGE->set_url('/mod/book/delete.php', ['id' => $id, 'chapterid' => $chapterid]);
if ($confirm) {
// The operation was confirmed.
$fs = get_file_storage();
$subchaptercount = 0;
if (!$chapter->subchapter) {
// This is a top-level chapter.
// Make sure to remove any sub-chapters if there are any.
$chapters = $DB->get_recordset_select('book_chapters', 'bookid = :bookid AND pagenum > :pagenum', [
'bookid' => $book->id,
'pagenum' => $chapter->pagenum,
], 'pagenum');
foreach ($chapters as $ch) {
if (!$ch->subchapter) {
// This is a new chapter. Any subsequent subchapters will be part of a different chapter.
break;
} else {
// This is subchapter of the chapter being removed.
core_tag_tag::remove_all_item_tags('mod_book', 'book_chapters', $ch->id);
$fs->delete_area_files($context->id, 'mod_book', 'chapter', $ch->id);
$DB->delete_records('book_chapters', ['id' => $ch->id]);
\mod_book\event\chapter_deleted::create_from_chapter($book, $context, $ch)->trigger();
$subchaptercount++;
}
}
$chapters->close();
}
// Now delete the actual chapter.
core_tag_tag::remove_all_item_tags('mod_book', 'book_chapters', $chapter->id);
$fs->delete_area_files($context->id, 'mod_book', 'chapter', $chapter->id);
$DB->delete_records('book_chapters', ['id' => $chapter->id]);
\mod_book\event\chapter_deleted::create_from_chapter($book, $context, $chapter)->trigger();
// Ensure that the book structure is correct.
// book_preload_chapters will fix parts including the pagenum.
$chapters = book_preload_chapters($book);
book_add_fake_block($chapters, $chapter, $book, $cm);
// Bump the book revision.
$DB->set_field('book', 'revision', $book->revision + 1, ['id' => $book->id]);
if ($subchaptercount) {
$message = get_string('chapterandsubchaptersdeleted', 'mod_book', (object) [
'title' => format_string($chapter->title),
'subchapters' => $subchaptercount,
]);
} else {
$message = get_string('chapterdeleted', 'mod_book', (object) [
'title' => format_string($chapter->title),
]);
}
redirect(new moodle_url('/mod/book/view.php', ['id' => $cm->id]), $message);
}
redirect(new moodle_url('/mod/book/view.php', ['id' => $cm->id]));
+51
View File
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* List of deprecated mod_book functions
*
* @package mod_book
* @copyright 2024 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* @deprecated since Moodle 3.8
*/
function book_scale_used() {
throw new coding_exception('book_scale_used() can not be used anymore. Plugins can implement ' .
'<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
}
/**
* @deprecated since Moodle 4.0
*/
function book_get_nav_types() {
throw new coding_exception(__FUNCTION__ . '() has been removed.');
}
/**
* Returns list of available navigation link CSS classes.
*
* @deprecated since Moodle 4.4.
* @todo MDL-81328 Final deprecation in Moodle 4.8.
* @return array
*/
function book_get_nav_classes() {
debugging(__FUNCTION__ . '() is deprecated. There is no replacement.');
return ['navtoc', 'navimages', 'navtext'];
}
+149
View File
@@ -0,0 +1,149 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Edit book chapter
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
require_once(__DIR__.'/edit_form.php');
$cmid = required_param('cmid', PARAM_INT); // Book Course Module ID
$chapterid = optional_param('id', 0, PARAM_INT); // Chapter ID
$pagenum = optional_param('pagenum', 0, PARAM_INT);
$subchapter = optional_param('subchapter', 0, PARAM_BOOL);
$cm = get_coursemodule_from_id('book', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/book:edit', $context);
$PAGE->set_url('/mod/book/edit.php', array('cmid'=>$cmid, 'id'=>$chapterid, 'pagenum'=>$pagenum, 'subchapter'=>$subchapter));
$PAGE->set_pagelayout('admin'); // TODO: Something. This is a bloody hack!
$PAGE->add_body_class('limitedwidth');
if ($chapterid) {
$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
$chapter->tags = core_tag_tag::get_item_tags_array('mod_book', 'book_chapters', $chapter->id);
} else {
$chapter = new stdClass();
$chapter->id = null;
$chapter->subchapter = $subchapter;
$chapter->pagenum = $pagenum + 1;
}
$chapter->cmid = $cm->id;
// Get the previous page number.
$prevpage = $chapter->pagenum - 1;
if ($prevpage) {
$currentchapter = $DB->get_record('book_chapters', ['pagenum' => $prevpage, 'bookid' => $book->id]);
if ($currentchapter) {
$chapter->currentchaptertitle = $currentchapter->title;
}
}
$options = array('noclean'=>true, 'subdirs'=>true, 'maxfiles'=>-1, 'maxbytes'=>0, 'context'=>$context);
$chapter = file_prepare_standard_editor($chapter, 'content', $options, $context, 'mod_book', 'chapter', $chapter->id);
$mform = new book_chapter_edit_form(null, array('chapter'=>$chapter, 'options'=>$options));
// If data submitted, then process and store.
if ($mform->is_cancelled()) {
// Make sure at least one chapter exists.
$chapters = book_preload_chapters($book);
if (!$chapters) {
redirect(new moodle_url('/course/view.php', array('id' => $course->id))); // Back to course view.
}
if (empty($chapter->id)) {
redirect("view.php?id=$cm->id");
} else {
redirect("view.php?id=$cm->id&chapterid=$chapter->id");
}
} else if ($data = $mform->get_data()) {
if ($data->id) {
// store the files
$data->timemodified = time();
$data = file_postupdate_standard_editor($data, 'content', $options, $context, 'mod_book', 'chapter', $data->id);
$DB->update_record('book_chapters', $data);
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
$chapter = $DB->get_record('book_chapters', array('id' => $data->id));
core_tag_tag::set_item_tags('mod_book', 'book_chapters', $chapter->id, $context, $data->tags);
\mod_book\event\chapter_updated::create_from_chapter($book, $context, $chapter)->trigger();
} else {
// adding new chapter
$data->bookid = $book->id;
$data->hidden = 0;
$data->timecreated = time();
$data->timemodified = time();
$data->importsrc = '';
$data->content = ''; // updated later
$data->contentformat = FORMAT_HTML; // updated later
// make room for new page
$sql = "UPDATE {book_chapters}
SET pagenum = pagenum + 1
WHERE bookid = ? AND pagenum >= ?";
$DB->execute($sql, array($book->id, $data->pagenum));
$data->id = $DB->insert_record('book_chapters', $data);
// store the files
$data = file_postupdate_standard_editor($data, 'content', $options, $context, 'mod_book', 'chapter', $data->id);
$DB->update_record('book_chapters', $data);
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
$chapter = $DB->get_record('book_chapters', array('id' => $data->id));
core_tag_tag::set_item_tags('mod_book', 'book_chapters', $chapter->id, $context, $data->tags);
\mod_book\event\chapter_created::create_from_chapter($book, $context, $chapter)->trigger();
}
book_preload_chapters($book); // fix structure
redirect("view.php?id=$cm->id&chapterid=$data->id");
}
// Otherwise fill and print the form.
$PAGE->set_title($book->name);
$PAGE->set_heading($course->fullname);
$PAGE->set_secondary_active_tab('modulepage');
if ($chapters = book_preload_chapters($book)) {
book_add_fake_block($chapters, $chapter, $book, $cm);
}
$PAGE->activityheader->set_attrs([
"description" => '',
"hidecompletion" => true
]);
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
+98
View File
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Chapter edit form
*
* @package mod_book
* @copyright 2004-2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir.'/formslib.php');
class book_chapter_edit_form extends moodleform {
function definition() {
global $CFG;
$chapter = $this->_customdata['chapter'];
$options = $this->_customdata['options'];
// Disabled subchapter option when editing first node.
$disabledmsg = null;
if ($chapter->pagenum == 1) {
$disabledmsg = get_string('subchapternotice', 'book');
}
$mform = $this->_form;
if (!empty($chapter->id)) {
$mform->addElement('header', 'general', get_string('editingchapter', 'mod_book'));
} else {
$mform->addElement('header', 'general', get_string('addafter', 'mod_book'));
}
if (isset($chapter->currentchaptertitle)) {
$mform->addElement('static', 'details',
get_string('previouschapter', 'mod_book'),
trim(format_string($chapter->currentchaptertitle, true))
);
}
$mform->addElement('text', 'title', get_string('chaptertitle', 'mod_book'),
['size' => '30', 'maxlength' => '255']);
$mform->setType('title', PARAM_RAW);
$mform->addRule('title', null, 'required', null, 'client');
$mform->addRule('title', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$mform->addElement('advcheckbox', 'subchapter', get_string('subchapter', 'mod_book'), $disabledmsg);
$mform->addElement('editor', 'content_editor', get_string('content', 'mod_book'), null, $options);
$mform->setType('content_editor', PARAM_RAW);
$mform->addRule('content_editor', get_string('required'), 'required', null, 'client');
if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
}
$mform->addElement('tags', 'tags', get_string('tags'),
array('itemtype' => 'book_chapters', 'component' => 'mod_book'));
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'cmid');
$mform->setType('cmid', PARAM_INT);
$mform->addElement('hidden', 'pagenum');
$mform->setType('pagenum', PARAM_INT);
$this->add_action_buttons(true);
// set the defaults
$this->set_data($chapter);
}
function definition_after_data(){
$mform = $this->_form;
$pagenum = $mform->getElement('pagenum');
if ($pagenum->getValue() == 1) {
$mform->hardFreeze('subchapter');
}
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This page lists all the instances of book in a particular course
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$id = required_param('id', PARAM_INT); // Course ID.
$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
unset($id);
require_course_login($course, true);
$PAGE->set_pagelayout('incourse');
// Get all required strings
$strbooks = get_string('modulenameplural', 'mod_book');
$strbook = get_string('modulename', 'mod_book');
$strname = get_string('name');
$strintro = get_string('moduleintro');
$strlastmodified = get_string('lastmodified');
$PAGE->set_url('/mod/book/index.php', array('id' => $course->id));
$PAGE->set_title($course->shortname.': '.$strbooks);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add($strbooks);
echo $OUTPUT->header();
\mod_book\event\course_module_instance_list_viewed::create_from_course($course)->trigger();
// Get all the appropriate data
if (!$books = get_all_instances_in_course('book', $course)) {
notice(get_string('thereareno', 'moodle', $strbooks), "$CFG->wwwroot/course/view.php?id=$course->id");
die;
}
$usesections = course_format_uses_sections($course->format);
$table = new html_table();
$table->attributes['class'] = 'generaltable mod_index';
if ($usesections) {
$strsectionname = get_string('sectionname', 'format_'.$course->format);
$table->head = array ($strsectionname, $strname, $strintro);
$table->align = array ('center', 'left', 'left');
} else {
$table->head = array ($strlastmodified, $strname, $strintro);
$table->align = array ('left', 'left', 'left');
}
$modinfo = get_fast_modinfo($course);
$currentsection = '';
foreach ($books as $book) {
$cm = $modinfo->get_cm($book->coursemodule);
if ($usesections) {
$printsection = '';
if ($book->section !== $currentsection) {
if ($book->section) {
$printsection = get_section_name($course, $book->section);
}
if ($currentsection !== '') {
$table->data[] = 'hr';
}
$currentsection = $book->section;
}
} else {
$printsection = html_writer::tag('span', userdate($book->timemodified), array('class' => 'smallinfo'));
}
$class = $book->visible ? null : array('class' => 'dimmed'); // hidden modules are dimmed
$table->data[] = array (
$printsection,
html_writer::link(new moodle_url('view.php', array('id' => $cm->id)), format_string($book->name), $class),
format_module_intro('book', $book, $cm->id));
}
echo html_writer::table($table);
echo $OUTPUT->footer();
+121
View File
@@ -0,0 +1,121 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module language strings
*
* @package mod_book
* @copyright 2004-2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$string['modulename'] = 'Book';
$string['modulename_help'] = 'The book module enables a teacher to create a multi-page resource in a book-like format, with chapters and subchapters. Books can contain media files as well as text and are useful for displaying lengthy passages of information which can be broken down into sections.
A book may be used
* To display reading material for individual modules of study
* As a staff departmental handbook
* As a showcase portfolio of student work';
$string['modulename_link'] = 'mod/book/view';
$string['modulenameplural'] = 'Books';
$string['pluginname'] = 'Book';
$string['pluginadministration'] = 'Book administration';
$string['toc'] = 'Table of contents';
$string['chapterandsubchaptersdeleted'] = 'Chapter "{$a->title}" and its {$a->subchapters} subchapters were deleted';
$string['chapterdeleted'] = 'Chapter "{$a->title}" was deleted';
$string['customtitles'] = 'Custom titles';
$string['customtitles_help'] = 'Normally the chapter title is displayed in the table of contents (TOC) AND as a heading above the content.
If the custom titles checkbox is ticked, the chapter title is NOT displayed as a heading above the content. A different title (perhaps longer than the chapter title) may be entered as part of the content.';
$string['chapters'] = 'Chapters';
$string['chaptertitle'] = 'Chapter title';
$string['content'] = 'Content';
$string['deletechapter'] = 'Delete chapter "{$a}"';
$string['editingchapter'] = 'Editing chapter';
$string['eventchaptercreated'] = 'Chapter created';
$string['eventchapterdeleted'] = 'Chapter deleted';
$string['eventchapterupdated'] = 'Chapter updated';
$string['eventchapterviewed'] = 'Chapter viewed';
$string['editchapter'] = 'Edit chapter "{$a}"';
$string['hidechapter'] = 'Hide chapter "{$a}"';
$string['indicator:cognitivedepth'] = 'Book cognitive';
$string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in a Book resource.';
$string['indicator:cognitivedepthdef'] = 'Book cognitive';
$string['indicator:cognitivedepthdef_help'] = 'The participant has reached this percentage of the cognitive engagement offered by the Book activities during this analysis interval (Levels = No view, View)';
$string['indicator:cognitivedepthdef_link'] = 'Learning_analytics_indicators#Cognitive_depth';
$string['indicator:socialbreadth'] = 'Book social';
$string['indicator:socialbreadth_help'] = 'This indicator is based on the social breadth reached by the student in a Book resource.';
$string['indicator:socialbreadthdef'] = 'Book social';
$string['indicator:socialbreadthdef_help'] = 'The participant has reached this percentage of the social engagement offered by the Book activities during this analysis interval (Levels = No participation, Participant alone)';
$string['indicator:socialbreadthdef_link'] = 'Learning_analytics_indicators#Social_breadth';
$string['movechapterup'] = 'Move chapter up "{$a}"';
$string['movechapterdown'] = 'Move chapter down "{$a}"';
$string['privacy:metadata'] = 'The book activity module does not store any personal data.';
$string['search:activity'] = 'Book - resource information';
$string['search:chapter'] = 'Book - chapters';
$string['showchapter'] = 'Show chapter "{$a}"';
$string['subchapter'] = 'Subchapter';
$string['navoptions'] = 'Available options for navigational links';
$string['navoptions_desc'] = 'Options for displaying navigation on the book pages';
$string['navstyle'] = 'Style of navigation';
$string['navstyle_help'] = '* Images - Icons are used for navigation
* Text - Chapter titles are used for navigation';
$string['nocontent'] = 'No content has been added to this book yet.';
$string['numbering'] = 'Chapter formatting';
$string['numbering_help'] = '* None - Chapter and subchapter titles have no formatting
* Numbers - Chapters and subchapter titles are numbered 1, 1.1, 1.2, 2, ...
* Bullets - Subchapters are indented and displayed with bullets in the table of contents
* Indented - Subchapters are indented in the table of contents';
$string['numbering0'] = 'None';
$string['numbering1'] = 'Numbers';
$string['numbering2'] = 'Bullets';
$string['numbering3'] = 'Indented';
$string['numberingoptions'] = 'Available options for chapter formatting';
$string['numberingoptions_desc'] = 'Options for displaying chapters and subchapters in the table of contents';
$string['addafter'] = 'Add new chapter';
$string['addafterchapter'] = 'Add new chapter after "{$a->title}"';
$string['previouschapter'] = 'Previous chapter';
$string['confchapterdelete'] = 'Do you really want to delete this chapter?';
$string['confchapterdeleteall'] = 'Do you really want to delete this chapter and all its subchapters?';
$string['top'] = 'top';
$string['navprev'] = 'Previous';
$string['navprevtitle'] = 'Previous: {$a}';
$string['navnext'] = 'Next';
$string['navnexttitle'] = 'Next: {$a}';
$string['navexit'] = 'Exit book';
$string['book:addinstance'] = 'Add a new book';
$string['book:read'] = 'View book';
$string['book:edit'] = 'Edit book chapters';
$string['book:viewhiddenchapters'] = 'View hidden book chapters';
$string['errorchapter'] = 'Error reading chapter of book.';
$string['page-mod-book-x'] = 'Any book module page';
$string['subchapternotice'] = '(Only available once the first chapter has been created)';
$string['subplugintype_booktool'] = 'Book tool';
$string['subplugintype_booktool_plural'] = 'Book tools';
$string['removeallbooktags'] = 'Remove all book tags';
$string['tagarea_book_chapters'] = 'Book chapters';
$string['tagsdeleted'] = 'Book tags have been deleted';
// Deprecated since Moodle 4.4.
$string['navimages'] = 'Images';
$string['navtext'] = 'Text';
$string['navtoc'] = 'TOC Only';
+3
View File
@@ -0,0 +1,3 @@
navimages,mod_book
navtext,mod_book
navtoc,mod_book
+758
View File
@@ -0,0 +1,758 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module core interaction API
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__ . '/deprecatedlib.php');
/**
* Returns list of available numbering types
* @return array
*/
function book_get_numbering_types() {
global $CFG; // required for the include
require_once(__DIR__.'/locallib.php');
return array (
BOOK_NUM_NONE => get_string('numbering0', 'mod_book'),
BOOK_NUM_NUMBERS => get_string('numbering1', 'mod_book'),
BOOK_NUM_BULLETS => get_string('numbering2', 'mod_book'),
BOOK_NUM_INDENTED => get_string('numbering3', 'mod_book')
);
}
/**
* Add book instance.
*
* @param stdClass $data
* @param stdClass $mform
* @return int new book instance id
*/
function book_add_instance($data, $mform) {
global $DB;
$data->timecreated = time();
$data->timemodified = $data->timecreated;
if (!isset($data->customtitles)) {
$data->customtitles = 0;
}
$id = $DB->insert_record('book', $data);
$completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
\core_completion\api::update_completion_date_event($data->coursemodule, 'book', $id, $completiontimeexpected);
return $id;
}
/**
* Update book instance.
*
* @param stdClass $data
* @param stdClass $mform
* @return bool true
*/
function book_update_instance($data, $mform) {
global $DB;
$data->timemodified = time();
$data->id = $data->instance;
if (!isset($data->customtitles)) {
$data->customtitles = 0;
}
$DB->update_record('book', $data);
$book = $DB->get_record('book', array('id'=>$data->id));
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
$completiontimeexpected = !empty($data->completionexpected) ? $data->completionexpected : null;
\core_completion\api::update_completion_date_event($data->coursemodule, 'book', $book->id, $completiontimeexpected);
return true;
}
/**
* Delete book instance by activity id
*
* @param int $id
* @return bool success
*/
function book_delete_instance($id) {
global $DB;
if (!$book = $DB->get_record('book', array('id'=>$id))) {
return false;
}
$cm = get_coursemodule_from_instance('book', $id);
\core_completion\api::update_completion_date_event($cm->id, 'book', $id, null);
$DB->delete_records('book_chapters', array('bookid'=>$book->id));
$DB->delete_records('book', array('id'=>$book->id));
return true;
}
/**
* Given a course and a time, this module should find recent activity
* that has occurred in book activities and print it out.
*
* @param stdClass $course
* @param bool $viewfullnames
* @param int $timestart
* @return bool true if there was output, or false is there was none
*/
function book_print_recent_activity($course, $viewfullnames, $timestart) {
return false; // True if anything was printed, otherwise false
}
/**
* This function is used by the reset_course_userdata function in moodlelib.
* @param $data the data submitted from the reset course.
* @return array status array
*/
function book_reset_userdata($data) {
global $DB;
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
$status = [];
if (!empty($data->reset_book_tags)) {
// Loop through the books and remove the tags from the chapters.
if ($books = $DB->get_records('book', array('course' => $data->courseid))) {
foreach ($books as $book) {
if (!$cm = get_coursemodule_from_instance('book', $book->id)) {
continue;
}
$context = context_module::instance($cm->id);
core_tag_tag::delete_instances('mod_book', null, $context->id);
}
}
$status[] = [
'component' => get_string('modulenameplural', 'book'),
'item' => get_string('tagsdeleted', 'book'),
'error' => false
];
}
return $status;
}
/**
* The elements to add the course reset form.
*
* @param MoodleQuickForm $mform
*/
function book_reset_course_form_definition(&$mform) {
$mform->addElement('header', 'bookheader', get_string('modulenameplural', 'book'));
$mform->addElement('checkbox', 'reset_book_tags', get_string('removeallbooktags', 'book'));
}
/**
* No cron in book.
*
* @return bool
*/
function book_cron () {
return true;
}
/**
* No grading in book.
*
* @param int $bookid
* @return null
*/
function book_grades($bookid) {
return null;
}
/**
* Checks if scale is being used by any instance of book
*
* This is used to find out if scale used anywhere
*
* @param int $scaleid
* @return bool true if the scale is used by any book
*/
function book_scale_used_anywhere($scaleid) {
return false;
}
/**
* Return read actions.
*
* Note: This is not used by new logging system. Event with
* crud = 'r' and edulevel = LEVEL_PARTICIPATING will
* be considered as view action.
*
* @return array
*/
function book_get_view_actions() {
global $CFG; // necessary for includes
$return = array('view', 'view all');
$plugins = core_component::get_plugin_list('booktool');
foreach ($plugins as $plugin => $dir) {
if (file_exists("$dir/lib.php")) {
require_once("$dir/lib.php");
}
$function = 'booktool_'.$plugin.'_get_view_actions';
if (function_exists($function)) {
if ($actions = $function()) {
$return = array_merge($return, $actions);
}
}
}
return $return;
}
/**
* Return write actions.
*
* Note: This is not used by new logging system. Event with
* crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
* will be considered as post action.
*
* @return array
*/
function book_get_post_actions() {
global $CFG; // necessary for includes
$return = array('update');
$plugins = core_component::get_plugin_list('booktool');
foreach ($plugins as $plugin => $dir) {
if (file_exists("$dir/lib.php")) {
require_once("$dir/lib.php");
}
$function = 'booktool_'.$plugin.'_get_post_actions';
if (function_exists($function)) {
if ($actions = $function()) {
$return = array_merge($return, $actions);
}
}
}
return $return;
}
/**
* Supported features
*
* @param string $feature FEATURE_xx constant for requested feature
* @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
*/
function book_supports($feature) {
switch($feature) {
case FEATURE_MOD_ARCHETYPE: return MOD_ARCHETYPE_RESOURCE;
case FEATURE_GROUPS: return false;
case FEATURE_GROUPINGS: return false;
case FEATURE_MOD_INTRO: return true;
case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
case FEATURE_GRADE_HAS_GRADE: return false;
case FEATURE_GRADE_OUTCOMES: return false;
case FEATURE_BACKUP_MOODLE2: return true;
case FEATURE_SHOW_DESCRIPTION: return true;
case FEATURE_MOD_PURPOSE: return MOD_PURPOSE_CONTENT;
default: return null;
}
}
/**
* Adds module specific settings to the settings block
*
* @param settings_navigation $settingsnav The settings navigation object
* @param navigation_node $booknode The node to add module settings to
* @return void
*/
function book_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $booknode) {
global $USER, $OUTPUT;
if ($booknode->children->count() > 0) {
$firstkey = $booknode->children->get_key_list()[0];
} else {
$firstkey = null;
}
$params = $settingsnav->get_page()->url->params();
if ($settingsnav->get_page()->cm->modname === 'book' and !empty($params['id']) and !empty($params['chapterid'])
and has_capability('mod/book:edit', $settingsnav->get_page()->cm->context)) {
if (!empty($USER->editing)) {
$string = get_string("turneditingoff");
$edit = '0';
} else {
$string = get_string("turneditingon");
$edit = '1';
}
$url = new moodle_url('/mod/book/view.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid'], 'edit'=>$edit, 'sesskey'=>sesskey()));
$editnode = navigation_node::create($string, $url, navigation_node::TYPE_SETTING);
$editnode->set_show_in_secondary_navigation(false);
$booknode->add_node($editnode, $firstkey);
if (!$settingsnav->get_page()->theme->haseditswitch) {
$settingsnav->get_page()->set_button($OUTPUT->single_button($url, $string));
}
}
$plugins = core_component::get_plugin_list('booktool');
foreach ($plugins as $plugin => $dir) {
if (file_exists("$dir/lib.php")) {
require_once("$dir/lib.php");
}
$function = 'booktool_'.$plugin.'_extend_settings_navigation';
if (function_exists($function)) {
$function($settingsnav, $booknode);
}
}
}
/**
* Lists all browsable file areas
* @param object $course
* @param object $cm
* @param object $context
* @return array
*/
function book_get_file_areas($course, $cm, $context) {
$areas = array();
$areas['chapter'] = get_string('chapters', 'mod_book');
return $areas;
}
/**
* File browsing support for book module chapter area.
* @param object $browser
* @param object $areas
* @param object $course
* @param object $cm
* @param object $context
* @param string $filearea
* @param int $itemid
* @param string $filepath
* @param string $filename
* @return object file_info instance or null if not found
*/
function book_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
global $CFG, $DB;
// note: 'intro' area is handled in file_browser automatically
if (!has_capability('mod/book:read', $context)) {
return null;
}
if ($filearea !== 'chapter') {
return null;
}
require_once(__DIR__.'/locallib.php');
if (is_null($itemid)) {
return new book_file_info($browser, $course, $cm, $context, $areas, $filearea);
}
$fs = get_file_storage();
$filepath = is_null($filepath) ? '/' : $filepath;
$filename = is_null($filename) ? '.' : $filename;
if (!$storedfile = $fs->get_file($context->id, 'mod_book', $filearea, $itemid, $filepath, $filename)) {
return null;
}
// modifications may be tricky - may cause caching problems
$canwrite = has_capability('mod/book:edit', $context);
$chaptername = $DB->get_field('book_chapters', 'title', array('bookid'=>$cm->instance, 'id'=>$itemid));
$chaptername = format_string($chaptername, true, array('context'=>$context));
$urlbase = $CFG->wwwroot.'/pluginfile.php';
return new file_info_stored($browser, $context, $storedfile, $urlbase, $chaptername, true, true, $canwrite, false);
}
/**
* Serves the book attachments. Implements needed access control ;-)
*
* @param stdClass $course course object
* @param cm_info $cm course module object
* @param context $context context object
* @param string $filearea file area
* @param array $args extra arguments
* @param bool $forcedownload whether or not force download
* @param array $options additional options affecting the file serving
* @return bool false if file not found, does not return if found - just send the file
*/
function book_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
global $CFG, $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}
require_course_login($course, true, $cm);
if ($filearea !== 'chapter') {
return false;
}
if (!has_capability('mod/book:read', $context)) {
return false;
}
$chid = (int)array_shift($args);
if (!$book = $DB->get_record('book', array('id'=>$cm->instance))) {
return false;
}
if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chid, 'bookid'=>$book->id))) {
return false;
}
if ($chapter->hidden and !has_capability('mod/book:viewhiddenchapters', $context)) {
return false;
}
// Download the contents of a chapter as an html file.
if ($args[0] == 'index.html') {
$filename = "index.html";
// We need to rewrite the pluginfile URLs so the media filters can work.
$content = file_rewrite_pluginfile_urls($chapter->content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
$chapter->id);
$formatoptions = new stdClass;
$formatoptions->noclean = true;
$formatoptions->overflowdiv = true;
$formatoptions->context = $context;
$content = format_text($content, $chapter->contentformat, $formatoptions);
// Remove @@PLUGINFILE@@/.
$options = array('reverse' => true);
$content = file_rewrite_pluginfile_urls($content, 'webservice/pluginfile.php', $context->id, 'mod_book', 'chapter',
$chapter->id, $options);
$content = str_replace('@@PLUGINFILE@@/', '', $content);
$titles = "";
// Format the chapter titles.
if (!$book->customtitles) {
require_once(__DIR__.'/locallib.php');
$chapters = book_preload_chapters($book);
if (!$chapter->subchapter) {
$currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
// Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
$titles = "<h3>$currtitle</h3>";
} else {
$currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
$currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
// Note that we can't use the $OUTPUT->heading() in WS_SERVER mode.
$titles = "<h3>$currtitle</h3>";
$titles .= "<h4>$currsubtitle</h4>";
}
}
$content = $titles . $content;
send_file($content, $filename, 0, 0, true, true);
} else {
$fs = get_file_storage();
$relativepath = implode('/', $args);
$fullpath = "/$context->id/mod_book/chapter/$chid/$relativepath";
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
return false;
}
// Nasty hack because we do not have file revisions in book yet.
$lifetime = $CFG->filelifetime;
if ($lifetime > 60 * 10) {
$lifetime = 60 * 10;
}
// Finally send the file.
send_stored_file($file, $lifetime, 0, $forcedownload, $options);
}
}
/**
* Return a list of page types
*
* @param string $pagetype current page type
* @param stdClass $parentcontext Block's parent context
* @param stdClass $currentcontext Current context of block
* @return array
*/
function book_page_type_list($pagetype, $parentcontext, $currentcontext) {
$module_pagetype = array('mod-book-*'=>get_string('page-mod-book-x', 'mod_book'));
return $module_pagetype;
}
/**
* Export book resource contents
*
* @param stdClass $cm Course module object
* @param string $baseurl Base URL for file downloads
* @return array of file content
*/
function book_export_contents($cm, $baseurl) {
global $DB;
$contents = array();
$context = context_module::instance($cm->id);
$book = $DB->get_record('book', array('id' => $cm->instance), '*', MUST_EXIST);
$fs = get_file_storage();
$chapters = $DB->get_records('book_chapters', array('bookid' => $book->id), 'pagenum');
$structure = array();
$currentchapter = 0;
foreach ($chapters as $chapter) {
if ($chapter->hidden && !has_capability('mod/book:viewhiddenchapters', $context)) {
continue;
}
// Generate the book structure.
$thischapter = array(
"title" => format_string($chapter->title, true, array('context' => $context)),
"href" => $chapter->id . "/index.html",
"level" => 0,
"hidden" => $chapter->hidden,
"subitems" => array()
);
// Main chapter.
if (!$chapter->subchapter) {
$currentchapter = $chapter->pagenum;
$structure[$currentchapter] = $thischapter;
} else {
// Subchapter.
$thischapter['level'] = 1;
$structure[$currentchapter]["subitems"][] = $thischapter;
}
// Export the chapter contents.
// Main content (html).
$filename = 'index.html';
$chapterindexfile = array();
$chapterindexfile['type'] = 'file';
$chapterindexfile['filename'] = $filename;
// Each chapter in a subdirectory.
$chapterindexfile['filepath'] = "/{$chapter->id}/";
$chapterindexfile['filesize'] = 0;
$chapterindexfile['fileurl'] = moodle_url::make_webservice_pluginfile_url(
$context->id, 'mod_book', 'chapter', $chapter->id, '/', 'index.html')->out(false);
$chapterindexfile['timecreated'] = $chapter->timecreated;
$chapterindexfile['timemodified'] = $chapter->timemodified;
$chapterindexfile['content'] = format_string($chapter->title, true, array('context' => $context));
$chapterindexfile['sortorder'] = 0;
$chapterindexfile['userid'] = null;
$chapterindexfile['author'] = null;
$chapterindexfile['license'] = null;
$chapterindexfile['tags'] = \core_tag\external\util::get_item_tags('mod_book', 'book_chapters', $chapter->id);
$contents[] = $chapterindexfile;
// Chapter files (images usually).
$files = $fs->get_area_files($context->id, 'mod_book', 'chapter', $chapter->id, 'sortorder DESC, id ASC', false);
foreach ($files as $fileinfo) {
$file = array();
$file['type'] = 'file';
$file['filename'] = $fileinfo->get_filename();
$file['filepath'] = "/{$chapter->id}" . $fileinfo->get_filepath();
$file['filesize'] = $fileinfo->get_filesize();
$file['fileurl'] = moodle_url::make_webservice_pluginfile_url(
$context->id, 'mod_book', 'chapter', $chapter->id,
$fileinfo->get_filepath(), $fileinfo->get_filename())->out(false);
$file['timecreated'] = $fileinfo->get_timecreated();
$file['timemodified'] = $fileinfo->get_timemodified();
$file['sortorder'] = $fileinfo->get_sortorder();
$file['userid'] = $fileinfo->get_userid();
$file['author'] = $fileinfo->get_author();
$file['license'] = $fileinfo->get_license();
$file['mimetype'] = $fileinfo->get_mimetype();
$file['isexternalfile'] = $fileinfo->is_external_file();
if ($file['isexternalfile']) {
$file['repositorytype'] = $fileinfo->get_repository_type();
}
$contents[] = $file;
}
}
// First content is the structure in encoded JSON format.
$structurefile = array();
$structurefile['type'] = 'content';
$structurefile['filename'] = 'structure';
$structurefile['filepath'] = "/";
$structurefile['filesize'] = 0;
$structurefile['fileurl'] = null;
$structurefile['timecreated'] = $book->timecreated;
$structurefile['timemodified'] = $book->timemodified;
$structurefile['content'] = json_encode(array_values($structure));
$structurefile['sortorder'] = 0;
$structurefile['userid'] = null;
$structurefile['author'] = null;
$structurefile['license'] = null;
// Add it as first element.
array_unshift($contents, $structurefile);
return $contents;
}
/**
* Mark the activity completed (if required) and trigger the course_module_viewed event.
*
* @param stdClass $book book object
* @param stdClass $chapter chapter object
* @param bool $islaschapter is the las chapter of the book?
* @param stdClass $course course object
* @param stdClass $cm course module object
* @param stdClass $context context object
* @since Moodle 3.0
*/
function book_view($book, $chapter, $islastchapter, $course, $cm, $context) {
// First case, we are just opening the book.
if (empty($chapter)) {
\mod_book\event\course_module_viewed::create_from_book($book, $context)->trigger();
} else {
\mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter)->trigger();
if ($islastchapter) {
// We cheat a bit here in assuming that viewing the last page means the user viewed the whole book.
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
}
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function book_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB;
$context = $cm->context;
$updates = new stdClass();
if (!has_capability('mod/book:read', $context)) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
$select = 'bookid = :id AND (timecreated > :since1 OR timemodified > :since2)';
$params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
if (!has_capability('mod/book:viewhiddenchapters', $context)) {
$select .= ' AND hidden = 0';
}
$updates->entries = (object) array('updated' => false);
$entries = $DB->get_records_select('book_chapters', $select, $params, '', 'id');
if (!empty($entries)) {
$updates->entries->updated = true;
$updates->entries->itemids = array_keys($entries);
}
return $updates;
}
/**
* Get icon mapping for font-awesome.
*/
function mod_book_get_fontawesome_icon_map() {
return [
'mod_book:chapter' => 'fa-bookmark-o',
'mod_book:nav_prev' => 'fa-arrow-left',
'mod_book:nav_sep' => 'fa-minus',
'mod_book:add' => 'fa-plus',
'mod_book:nav_next' => 'fa-arrow-right',
'mod_book:nav_exit' => 'fa-arrow-up',
];
}
/**
* This function receives a calendar event and returns the action associated with it, or null if there is none.
*
* This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
* is not displayed on the block.
*
* @param calendar_event $event
* @param \core_calendar\action_factory $factory
* @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
* @return \core_calendar\local\event\entities\action_interface|null
*/
function mod_book_core_calendar_provide_event_action(calendar_event $event,
\core_calendar\action_factory $factory,
int $userid = 0) {
global $USER;
if (empty($userid)) {
$userid = $USER->id;
}
$cm = get_fast_modinfo($event->courseid, $userid)->instances['book'][$event->instance];
if (!$cm->uservisible) {
// The module is not visible to the user for any reason.
return null;
}
$context = context_module::instance($cm->id);
if (!has_capability('mod/book:read', $context, $userid)) {
return null;
}
$completion = new \completion_info($cm->get_course());
$completiondata = $completion->get_data($cm, false, $userid);
if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
return null;
}
return $factory->create_instance(
get_string('view'),
new \moodle_url('/mod/book/view.php', ['id' => $cm->id]),
1,
true
);
}
+696
View File
@@ -0,0 +1,696 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module local lib functions
*
* @package mod_book
* @copyright 2010-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__.'/lib.php');
require_once($CFG->libdir.'/filelib.php');
/**
* The following defines are used to define how the chapters and subchapters of a book should be displayed in that table of contents.
* BOOK_NUM_NONE No special styling will applied and the editor will be able to do what ever thay want in the title
* BOOK_NUM_NUMBERS Chapters and subchapters are numbered (1, 1.1, 1.2, 2, ...)
* BOOK_NUM_BULLETS Subchapters are indented and displayed with bullets
* BOOK_NUM_INDENTED Subchapters are indented
*/
define('BOOK_NUM_NONE', '0');
define('BOOK_NUM_NUMBERS', '1');
define('BOOK_NUM_BULLETS', '2');
define('BOOK_NUM_INDENTED', '3');
/**
* Preload book chapters and fix toc structure if necessary.
*
* Returns array of chapters with standard 'pagenum', 'id, pagenum, subchapter, title, content, contentformat, hidden'
* and extra 'parent, number, subchapters, prev, next'.
* Please note the content/text of chapters is not included.
*
* @param stdClass $book
* @return array of id=>chapter
*/
function book_preload_chapters($book) {
global $DB;
$chapters = $DB->get_records('book_chapters', array('bookid' => $book->id), 'pagenum', 'id, pagenum,
subchapter, title, content, contentformat, hidden');
if (!$chapters) {
return array();
}
$prev = null;
$prevsub = null;
$first = true;
$hidesub = true;
$parent = null;
$pagenum = 0; // chapter sort
$i = 0; // main chapter num
$j = 0; // subchapter num
foreach ($chapters as $id => $ch) {
$oldch = clone($ch);
$pagenum++;
$ch->pagenum = $pagenum;
if ($first) {
// book can not start with a subchapter
$ch->subchapter = 0;
$first = false;
}
if (!$ch->subchapter) {
if ($ch->hidden) {
if ($book->numbering == BOOK_NUM_NUMBERS) {
$ch->number = 'x';
} else {
$ch->number = null;
}
} else {
$i++;
$ch->number = $i;
}
$j = 0;
$prevsub = null;
$hidesub = $ch->hidden;
$parent = $ch->id;
$ch->parent = null;
$ch->subchapters = array();
} else {
$ch->parent = $parent;
$ch->subchapters = null;
$chapters[$parent]->subchapters[$ch->id] = $ch->id;
if ($hidesub) {
// all subchapters in hidden chapter must be hidden too
$ch->hidden = 1;
}
if ($ch->hidden) {
if ($book->numbering == BOOK_NUM_NUMBERS) {
$ch->number = 'x';
} else {
$ch->number = null;
}
} else {
$j++;
$ch->number = $j;
}
}
if ($oldch->subchapter != $ch->subchapter or $oldch->pagenum != $ch->pagenum or $oldch->hidden != $ch->hidden) {
// update only if something changed
$DB->update_record('book_chapters', $ch);
}
$chapters[$id] = $ch;
}
return $chapters;
}
/**
* Returns the title for a given chapter
*
* @param int $chid
* @param array $chapters
* @param stdClass $book
* @param context_module $context
* @return string
*/
function book_get_chapter_title($chid, $chapters, $book, $context) {
$ch = $chapters[$chid];
$title = trim(format_string($ch->title, true, array('context'=>$context)));
$numbers = array();
if ($book->numbering == BOOK_NUM_NUMBERS) {
if ($ch->parent and $chapters[$ch->parent]->number) {
$numbers[] = $chapters[$ch->parent]->number;
}
if ($ch->number) {
$numbers[] = $ch->number;
}
}
if ($numbers) {
$title = implode('.', $numbers) . '. ' . $title;
}
return $title;
}
/**
* Add the book TOC sticky block to the default region.
*
* @param array $chapters The Chapters in the book
* @param stdClass $chapter The current chapter
* @param stdClass $book The book
* @param stdClass $cm The course module
* @param bool|null $edit Whether the user is editing
*/
function book_add_fake_block($chapters, $chapter, $book, $cm, $edit = null) {
global $PAGE, $USER;
if ($edit === null) {
if (has_capability('mod/book:edit', context_module::instance($cm->id))) {
if (isset($USER->editing)) {
$edit = $USER->editing;
} else {
$edit = 0;
}
} else {
$edit = 0;
}
}
$toc = book_get_toc($chapters, $chapter, $book, $cm, $edit);
$bc = new block_contents();
$bc->title = get_string('toc', 'mod_book');
$bc->attributes['class'] = 'block block_book_toc';
$bc->content = $toc;
$defaultregion = $PAGE->blocks->get_default_region();
$PAGE->blocks->add_fake_block($bc, $defaultregion);
}
/**
* Generate toc structure
*
* @param array $chapters
* @param stdClass $chapter
* @param stdClass $book
* @param stdClass $cm
* @param bool $edit
* @return string
*/
function book_get_toc($chapters, $chapter, $book, $cm, $edit) {
global $USER, $OUTPUT;
$toc = '';
$nch = 0; // Chapter number
$ns = 0; // Subchapter number
$first = 1;
$context = context_module::instance($cm->id);
$viewhidden = has_capability('mod/book:viewhiddenchapters', $context);
switch ($book->numbering) {
case BOOK_NUM_NONE:
$toc .= html_writer::start_tag('div', array('class' => 'book_toc book_toc_none clearfix'));
break;
case BOOK_NUM_NUMBERS:
$toc .= html_writer::start_tag('div', array('class' => 'book_toc book_toc_numbered clearfix'));
break;
case BOOK_NUM_BULLETS:
$toc .= html_writer::start_tag('div', array('class' => 'book_toc book_toc_bullets clearfix'));
break;
case BOOK_NUM_INDENTED:
$toc .= html_writer::start_tag('div', array('class' => 'book_toc book_toc_indented clearfix'));
break;
}
if ($edit) { // Editing on (Teacher's TOC).
$toc .= html_writer::start_tag('ul');
$i = 0;
foreach ($chapters as $ch) {
$i++;
$title = trim(format_string($ch->title, true, array('context' => $context)));
$titleunescaped = trim(format_string($ch->title, true, array('context' => $context, 'escape' => false)));
$titleout = $title;
if (!$ch->subchapter) {
if ($first) {
$toc .= html_writer::start_tag('li');
} else {
$toc .= html_writer::end_tag('ul');
$toc .= html_writer::end_tag('li');
$toc .= html_writer::start_tag('li');
}
if (!$ch->hidden) {
$nch++;
$ns = 0;
if ($book->numbering == BOOK_NUM_NUMBERS) {
$title = "$nch. $title";
$titleout = $title;
}
} else {
if ($book->numbering == BOOK_NUM_NUMBERS) {
$title = "x. $title";
}
$titleout = html_writer::tag('span', $title, array('class' => 'dimmed_text'));
}
} else {
if ($first) {
$toc .= html_writer::start_tag('li');
$toc .= html_writer::start_tag('ul');
$toc .= html_writer::start_tag('li');
} else {
$toc .= html_writer::start_tag('li');
}
if (!$ch->hidden) {
$ns++;
if ($book->numbering == BOOK_NUM_NUMBERS) {
$title = "$nch.$ns. $title";
$titleout = $title;
}
} else {
if ($book->numbering == BOOK_NUM_NUMBERS) {
if (empty($chapters[$ch->parent]->hidden)) {
$title = "$nch.x. $title";
} else {
$title = "x.x. $title";
}
}
$titleout = html_writer::tag('span', $title, array('class' => 'dimmed_text'));
}
}
$toc .= html_writer::start_tag('div', array('class' => 'd-flex'));
if ($ch->id == $chapter->id) {
$toc .= html_writer::tag('strong', $titleout, array('class' => 'text-truncate'));
} else {
$toc .= html_writer::link(new moodle_url('view.php', array('id' => $cm->id, 'chapterid' => $ch->id)), $titleout,
array('title' => $titleunescaped, 'class' => 'text-truncate'));
}
$toc .= html_writer::start_tag('div', array('class' => 'action-list d-flex ml-auto'));
if ($i != 1) {
$toc .= html_writer::link(new moodle_url('move.php', array('id' => $cm->id, 'chapterid' => $ch->id, 'up' => '1', 'sesskey' => $USER->sesskey)),
$OUTPUT->pix_icon('t/up', get_string('movechapterup', 'mod_book', $title)),
array('title' => get_string('movechapterup', 'mod_book', $titleunescaped)));
}
if ($i != count($chapters)) {
$toc .= html_writer::link(new moodle_url('move.php', array('id' => $cm->id, 'chapterid' => $ch->id, 'up' => '0', 'sesskey' => $USER->sesskey)),
$OUTPUT->pix_icon('t/down', get_string('movechapterdown', 'mod_book', $title)),
array('title' => get_string('movechapterdown', 'mod_book', $titleunescaped)));
}
$toc .= html_writer::link(new moodle_url('edit.php', array('cmid' => $cm->id, 'id' => $ch->id)),
$OUTPUT->pix_icon('t/edit', get_string('editchapter', 'mod_book', $title)),
array('title' => get_string('editchapter', 'mod_book', $titleunescaped)));
$deleteaction = new confirm_action(get_string('deletechapter', 'mod_book', $titleunescaped));
$toc .= $OUTPUT->action_icon(
new moodle_url('delete.php', [
'id' => $cm->id,
'chapterid' => $ch->id,
'sesskey' => sesskey(),
'confirm' => 1,
]),
new pix_icon('t/delete', get_string('deletechapter', 'mod_book', $title)),
$deleteaction,
['title' => get_string('deletechapter', 'mod_book', $titleunescaped)]
);
if ($ch->hidden) {
$toc .= html_writer::link(new moodle_url('show.php', array('id' => $cm->id, 'chapterid' => $ch->id, 'sesskey' => $USER->sesskey)),
$OUTPUT->pix_icon('t/show', get_string('showchapter', 'mod_book', $title)),
array('title' => get_string('showchapter', 'mod_book', $titleunescaped)));
} else {
$toc .= html_writer::link(new moodle_url('show.php', array('id' => $cm->id, 'chapterid' => $ch->id, 'sesskey' => $USER->sesskey)),
$OUTPUT->pix_icon('t/hide', get_string('hidechapter', 'mod_book', $title)),
array('title' => get_string('hidechapter', 'mod_book', $titleunescaped)));
}
$buttontitle = get_string('addafterchapter', 'mod_book', ['title' => $ch->title]);
$toc .= html_writer::link(new moodle_url('edit.php', array('cmid' => $cm->id, 'pagenum' => $ch->pagenum, 'subchapter' => $ch->subchapter)),
$OUTPUT->pix_icon('add', $buttontitle, 'mod_book'), array('title' => $buttontitle));
$toc .= html_writer::end_tag('div');
$toc .= html_writer::end_tag('div');
if (!$ch->subchapter) {
$toc .= html_writer::start_tag('ul');
} else {
$toc .= html_writer::end_tag('li');
}
$first = 0;
}
$toc .= html_writer::end_tag('ul');
$toc .= html_writer::end_tag('li');
$toc .= html_writer::end_tag('ul');
} else { // Editing off. Normal students, teachers view.
$toc .= html_writer::start_tag('ul');
foreach ($chapters as $ch) {
$title = trim(format_string($ch->title, true, array('context'=>$context)));
$titleunescaped = trim(format_string($ch->title, true, array('context' => $context, 'escape' => false)));
if (!$ch->hidden || ($ch->hidden && $viewhidden)) {
if (!$ch->subchapter) {
$nch++;
$ns = 0;
if ($first) {
$toc .= html_writer::start_tag('li');
} else {
$toc .= html_writer::end_tag('ul');
$toc .= html_writer::end_tag('li');
$toc .= html_writer::start_tag('li');
}
if ($book->numbering == BOOK_NUM_NUMBERS) {
$title = "$nch. $title";
}
} else {
$ns++;
if ($first) {
$toc .= html_writer::start_tag('li');
$toc .= html_writer::start_tag('ul');
$toc .= html_writer::start_tag('li');
} else {
$toc .= html_writer::start_tag('li');
}
if ($book->numbering == BOOK_NUM_NUMBERS) {
$title = "$nch.$ns. $title";
}
}
$cssclass = ($ch->hidden && $viewhidden) ? 'dimmed_text' : '';
if ($ch->id == $chapter->id) {
$toc .= html_writer::tag('strong', $title, array('class' => $cssclass));
} else {
$toc .= html_writer::link(new moodle_url('view.php',
array('id' => $cm->id, 'chapterid' => $ch->id)),
$title, array('title' => s($titleunescaped), 'class' => $cssclass));
}
if (!$ch->subchapter) {
$toc .= html_writer::start_tag('ul');
} else {
$toc .= html_writer::end_tag('li');
}
$first = 0;
}
}
$toc .= html_writer::end_tag('ul');
$toc .= html_writer::end_tag('li');
$toc .= html_writer::end_tag('ul');
}
$toc .= html_writer::end_tag('div');
$toc = str_replace('<ul></ul>', '', $toc); // Cleanup of invalid structures.
return $toc;
}
/**
* Returns book chapters tagged with a specified tag.
*
* This is a callback used by the tag area mod_book/book_chapters to search for book chapters
* tagged with a specific tag.
*
* @param core_tag_tag $tag
* @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
* are displayed on the page and the per-page limit may be bigger
* @param int $fromctx context id where the link was displayed, may be used by callbacks
* to display items in the same context first
* @param int $ctx context id where to search for records
* @param bool $rec search in subcontexts as well
* @param int $page 0-based number of page being displayed
* @return \core_tag\output\tagindex
*/
function mod_book_get_tagged_chapters($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = true, $page = 0) {
global $OUTPUT;
$perpage = $exclusivemode ? 20 : 5;
// Build the SQL query.
$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
$query = "SELECT bc.id, bc.title, bc.bookid, bc.hidden,
cm.id AS cmid, c.id AS courseid, c.shortname, c.fullname, $ctxselect
FROM {book_chapters} bc
JOIN {book} b ON b.id = bc.bookid
JOIN {modules} m ON m.name='book'
JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = b.id
JOIN {tag_instance} tt ON bc.id = tt.itemid
JOIN {course} c ON cm.course = c.id
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel
WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component
AND cm.deletioninprogress = 0
AND bc.id %ITEMFILTER% AND c.id %COURSEFILTER%";
$params = array('itemtype' => 'book_chapters', 'tagid' => $tag->id, 'component' => 'mod_book',
'coursemodulecontextlevel' => CONTEXT_MODULE);
if ($ctx) {
$context = $ctx ? context::instance_by_id($ctx) : context_system::instance();
$query .= $rec ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid';
$params['contextid'] = $context->id;
$params['path'] = $context->path.'/%';
}
$query .= " ORDER BY ";
if ($fromctx) {
// In order-clause specify that modules from inside "fromctx" context should be returned first.
$fromcontext = context::instance_by_id($fromctx);
$query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),';
$params['fromcontextid'] = $fromcontext->id;
$params['frompath'] = $fromcontext->path.'/%';
}
$query .= ' c.sortorder, cm.id, bc.id';
$totalpages = $page + 1;
// Use core_tag_index_builder to build and filter the list of items.
$builder = new core_tag_index_builder('mod_book', 'book_chapters', $query, $params, $page * $perpage, $perpage + 1);
while ($item = $builder->has_item_that_needs_access_check()) {
context_helper::preload_from_record($item);
$courseid = $item->courseid;
if (!$builder->can_access_course($courseid)) {
$builder->set_accessible($item, false);
continue;
}
$modinfo = get_fast_modinfo($builder->get_course($courseid));
// Set accessibility of this item and all other items in the same course.
$builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder) {
if ($taggeditem->courseid == $courseid) {
$accessible = false;
if (($cm = $modinfo->get_cm($taggeditem->cmid)) && $cm->uservisible) {
if (empty($taggeditem->hidden)) {
$accessible = true;
} else {
$accessible = has_capability('mod/book:viewhiddenchapters', context_module::instance($cm->id));
}
}
$builder->set_accessible($taggeditem, $accessible);
}
});
}
$items = $builder->get_items();
if (count($items) > $perpage) {
$totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists.
array_pop($items);
}
// Build the display contents.
if ($items) {
$tagfeed = new core_tag\output\tagfeed();
foreach ($items as $item) {
context_helper::preload_from_record($item);
$modinfo = get_fast_modinfo($item->courseid);
$cm = $modinfo->get_cm($item->cmid);
$pageurl = new moodle_url('/mod/book/view.php', array('chapterid' => $item->id, 'b' => $item->bookid));
$pagename = format_string($item->title, true, array('context' => context_module::instance($item->cmid)));
$pagename = html_writer::link($pageurl, $pagename);
$courseurl = course_get_url($item->courseid, $cm->sectionnum);
$cmname = html_writer::link($cm->url, $cm->get_formatted_name());
$coursename = format_string($item->fullname, true, array('context' => context_course::instance($item->courseid)));
$coursename = html_writer::link($courseurl, $coursename);
$icon = html_writer::link($pageurl, html_writer::empty_tag('img', array('src' => $cm->get_icon_url())));
$tagfeed->add($icon, $pagename, $cmname.'<br>'.$coursename);
}
$content = $OUTPUT->render_from_template('core_tag/tagfeed',
$tagfeed->export_for_template($OUTPUT));
return new core_tag\output\tagindex($tag, 'mod_book', 'book_chapters', $content,
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
}
}
/**
* File browsing support class
*
* @copyright 2010-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class book_file_info extends file_info {
/** @var stdClass Course object */
protected $course;
/** @var stdClass Course module object */
protected $cm;
/** @var array Available file areas */
protected $areas;
/** @var string File area to browse */
protected $filearea;
/**
* Constructor
*
* @param file_browser $browser file_browser instance
* @param stdClass $course course object
* @param stdClass $cm course module object
* @param stdClass $context module context
* @param array $areas available file areas
* @param string $filearea file area to browse
*/
public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
parent::__construct($browser, $context);
$this->course = $course;
$this->cm = $cm;
$this->areas = $areas;
$this->filearea = $filearea;
}
/**
* Returns list of standard virtual file/directory identification.
* The difference from stored_file parameters is that null values
* are allowed in all fields
* @return array with keys contextid, filearea, itemid, filepath and filename
*/
public function get_params() {
return array('contextid'=>$this->context->id,
'component'=>'mod_book',
'filearea' =>$this->filearea,
'itemid' =>null,
'filepath' =>null,
'filename' =>null);
}
/**
* Returns localised visible name.
* @return string
*/
public function get_visible_name() {
return $this->areas[$this->filearea];
}
/**
* Can I add new files or directories?
* @return bool
*/
public function is_writable() {
return false;
}
/**
* Is directory?
* @return bool
*/
public function is_directory() {
return true;
}
/**
* Returns list of children.
* @return array of file_info instances
*/
public function get_children() {
return $this->get_filtered_children('*', false, true);
}
/**
* Help function to return files matching extensions or their count
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @param bool|int $countonly if false returns the children, if an int returns just the
* count of children but stops counting when $countonly number of children is reached
* @param bool $returnemptyfolders if true returns items that don't have matching files inside
* @return array|int array of file_info instances or the count
*/
private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) {
global $DB;
$params = array('contextid' => $this->context->id,
'component' => 'mod_book',
'filearea' => $this->filearea,
'bookid' => $this->cm->instance);
$sql = 'SELECT DISTINCT bc.id, bc.pagenum
FROM {files} f, {book_chapters} bc
WHERE f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea
AND bc.bookid = :bookid
AND bc.id = f.itemid';
if (!$returnemptyfolders) {
$sql .= ' AND filename <> :emptyfilename';
$params['emptyfilename'] = '.';
}
list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f');
$sql .= ' '.$sql2;
$params = array_merge($params, $params2);
if ($countonly === false) {
$sql .= ' ORDER BY bc.pagenum';
}
$rs = $DB->get_recordset_sql($sql, $params);
$children = array();
foreach ($rs as $record) {
if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $record->id)) {
if ($returnemptyfolders || $child->count_non_empty_children($extensions)) {
$children[] = $child;
}
}
if ($countonly !== false && count($children) >= $countonly) {
break;
}
}
$rs->close();
if ($countonly !== false) {
return count($children);
}
return $children;
}
/**
* Returns list of children which are either files matching the specified extensions
* or folders that contain at least one such file.
*
* @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg')
* @return array of file_info instances
*/
public function get_non_empty_children($extensions = '*') {
return $this->get_filtered_children($extensions, false);
}
/**
* Returns the number of children which are either files matching the specified extensions
* or folders containing at least one such file.
*
* @param string|array $extensions, for example '*' or array('.gif','.jpg')
* @param int $limit stop counting after at least $limit non-empty children are found
* @return int
*/
public function count_non_empty_children($extensions = '*', $limit = 1) {
return $this->get_filtered_children($extensions, $limit);
}
/**
* Returns parent file_info instance
* @return file_info or null for root
*/
public function get_parent() {
return $this->browser->get_file_info($this->context);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Instance add/edit form
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__.'/locallib.php');
require_once($CFG->dirroot.'/course/moodleform_mod.php');
class mod_book_mod_form extends moodleform_mod {
function definition() {
global $CFG;
$mform = $this->_form;
$config = get_config('book');
$mform->addElement('header', 'general', get_string('general', 'form'));
$mform->addElement('text', 'name', get_string('name'), array('size'=>'64'));
if (!empty($CFG->formatstringstriptags)) {
$mform->setType('name', PARAM_TEXT);
} else {
$mform->setType('name', PARAM_CLEANHTML);
}
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
$this->standard_intro_elements(get_string('moduleintro'));
// Appearance.
$mform->addElement('header', 'appearancehdr', get_string('appearance'));
$alloptions = book_get_numbering_types();
$allowed = explode(',', $config->numberingoptions);
$options = array();
foreach ($allowed as $type) {
if (isset($alloptions[$type])) {
$options[$type] = $alloptions[$type];
}
}
if ($this->current->instance) {
if (!isset($options[$this->current->numbering])) {
if (isset($alloptions[$this->current->numbering])) {
$options[$this->current->numbering] = $alloptions[$this->current->numbering];
}
}
}
$mform->addElement('select', 'numbering', get_string('numbering', 'book'), $options);
$mform->addHelpButton('numbering', 'numbering', 'mod_book');
$mform->setDefault('numbering', $config->numbering);
$mform->addElement('static', 'customtitlestext', get_string('customtitles', 'mod_book'));
$mform->addElement('checkbox', 'customtitles', get_string('customtitles', 'book'));
$mform->addHelpButton('customtitles', 'customtitles', 'mod_book');
$mform->setDefault('customtitles', 0);
$this->standard_coursemodule_elements();
$this->add_action_buttons();
}
}
+188
View File
@@ -0,0 +1,188 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Move book chapter
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$id = required_param('id', PARAM_INT); // Course Module ID
$chapterid = required_param('chapterid', PARAM_INT); // Chapter ID
$up = optional_param('up', 0, PARAM_BOOL);
$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
require_sesskey();
$context = context_module::instance($cm->id);
require_capability('mod/book:edit', $context);
$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
$oldchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, pagenum, subchapter');
$nothing = 0;
$chapters = array();
$chs = 0;
$che = 0;
$ts = 0;
$te = 0;
// create new ordered array and find chapters to be moved
$i = 1;
$found = 0;
foreach ($oldchapters as $ch) {
$chapters[$i] = $ch;
if ($chapter->id == $ch->id) {
$chs = $i;
$che = $chs;
if ($ch->subchapter) {
$found = 1;// Subchapter moves alone.
}
} else if ($chs) {
if ($found) {
// Nothing.
} else if ($ch->subchapter) {
$che = $i; // Chapter with subchapter(s).
} else {
$found = 1;
}
}
$i++;
}
// Find target chapter(s).
if ($chapters[$chs]->subchapter) { // Moving single subchapter up or down.
if ($up) {
if ($chs == 1) {
$nothing = 1; // Already first.
} else {
$ts = $chs - 1;
$te = $ts;
}
} else { // Down.
if ($che == count($chapters)) {
$nothing = 1; // Already last.
} else {
$ts = $che + 1;
$te = $ts;
}
}
} else { // Moving chapter and looking for next/previous chapter.
if ($up) { // Up.
if ($chs == 1) {
$nothing = 1; // Already first.
} else {
$te = $chs - 1;
for ($i = $chs-1; $i >= 1; $i--) {
if ($chapters[$i]->subchapter) {
$ts = $i;
} else {
$ts = $i;
break;
}
}
}
} else { // Down.
if ($che == count($chapters)) {
$nothing = 1; // Already last.
} else {
$ts = $che + 1;
$found = 0;
for ($i = $che+1; $i <= count($chapters); $i++) {
if ($chapters[$i]->subchapter) {
$te = $i;
} else {
if ($found) {
break;
} else {
$te = $i;
$found = 1;
}
}
}
}
}
}
// Recreated newly sorted list of chapters.
if (!$nothing) {
$newchapters = array();
if ($up) {
if ($ts > 1) {
for ($i=1; $i<$ts; $i++) {
$newchapters[] = $chapters[$i];
}
}
for ($i=$chs; $i<=$che; $i++) {
$newchapters[$i] = $chapters[$i];
}
for ($i=$ts; $i<=$te; $i++) {
$newchapters[$i] = $chapters[$i];
}
if ($che<count($chapters)) {
for ($i=$che; $i<=count($chapters); $i++) {
$newchapters[$i] = $chapters[$i];
}
}
} else {
if ($chs > 1) {
for ($i=1; $i<$chs; $i++) {
$newchapters[] = $chapters[$i];
}
}
for ($i=$ts; $i<=$te; $i++) {
$newchapters[$i] = $chapters[$i];
}
for ($i=$chs; $i<=$che; $i++) {
$newchapters[$i] = $chapters[$i];
}
if ($te<count($chapters)) {
for ($i=$te; $i<=count($chapters); $i++) {
$newchapters[$i] = $chapters[$i];
}
}
}
// Store chapters in the new order.
$i = 1;
foreach ($newchapters as $ch) {
$ch->pagenum = $i;
$DB->update_record('book_chapters', $ch);
$ch = $DB->get_record('book_chapters', array('id' => $ch->id));
\mod_book\event\chapter_updated::create_from_chapter($book, $context, $ch)->trigger();
$i++;
}
}
book_preload_chapters($book); // fix structure
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
redirect('view.php?id='.$cm->id.'&chapterid='.$chapter->id);
Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M11 4.5H7.5V1c0-.5-.5-1-1-1h-1c-.5 0-1 .5-1 1v3.5H1c-.5 0-1 .5-1 1v1c0 .5.5 1 1 1h3.5V11c0 .5.5 1 1 1h1c.5 0 1-.5 1-1V7.5H11c.6 0 1-.5 1-1v-1c0-.5-.4-1-1-1z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>

After

Width:  |  Height:  |  Size: 514 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36863 5.40776C6.36961 4.58003 7.04089 3.90955 7.86862 3.90955H9.66773C10.8259 3.90955 11.8019 4.60291 12.3574 5.54237C12.9225 4.60152 13.9125 3.90955 15.0677 3.90955H16.8746C17.7037 3.90955 18.3756 4.58221 18.3746 5.41133L18.374 5.91231L19.8467 5.91234C20.6747 5.91235 21.3461 6.58333 21.3467 7.41136L21.3532 17.4072C21.3537 18.236 20.682 18.9082 19.8532 18.9082L14.161 18.9082C14.0704 18.9082 13.9812 18.905 13.892 18.9018C13.6932 18.8948 13.4941 18.8877 13.2783 18.9156C12.9741 18.9548 12.8472 19.0448 12.7957 19.1428C12.7098 19.306 12.5411 19.4087 12.3567 19.4101C12.1723 19.4114 12.0022 19.3111 11.914 19.1491C11.8599 19.0498 11.7291 18.9599 11.4274 18.9194C11.2105 18.8903 11.0072 18.8967 10.8058 18.9029C10.7215 18.9055 10.6376 18.9082 10.5531 18.9082H6.95315L6.95066 18.9081L4.84558 18.8977C4.02007 18.8936 3.35303 18.2232 3.35303 17.3977V7.40205C3.35303 6.57062 4.0293 5.8978 4.86072 5.90207L6.36803 5.9098L6.36863 5.40776ZM6.36684 6.9098L4.85559 6.90205C4.57845 6.90063 4.35303 7.1249 4.35303 7.40205V17.3977C4.35303 17.6729 4.57538 17.8963 4.85054 17.8977L6.95315 17.9082H10.5531C10.5892 17.9082 10.6391 17.9066 10.6991 17.9047C10.9119 17.8979 11.2526 17.887 11.5603 17.9283C11.5825 17.9313 11.6053 17.9346 11.6284 17.9383C11.5345 17.7957 11.4144 17.6773 11.2743 17.5786C10.8983 17.3136 10.3367 17.1639 9.65314 17.1639H7.85642C7.0273 17.1639 6.35544 16.4913 6.35642 15.6622L6.36684 6.9098ZM13.081 17.9336C13.1045 17.93 13.1276 17.9267 13.1502 17.9238C13.4554 17.8844 13.7986 17.8966 14.0115 17.9041C14.0739 17.9063 14.1252 17.9082 14.161 17.9082L19.8532 17.9082C20.1295 17.9082 20.3534 17.6841 20.3532 17.4079L20.3467 7.41201C20.3465 7.136 20.1227 6.91234 19.8467 6.91234L18.3728 6.91231L18.3624 15.6657C18.3614 16.4935 17.6901 17.1639 16.8624 17.1639H15.0531C14.3696 17.1639 13.808 17.3136 13.432 17.5786C13.2934 17.6763 13.1744 17.7931 13.081 17.9336ZM17.3736 6.30145C17.3655 6.3371 17.3612 6.3742 17.3612 6.41229C17.3612 6.44997 17.3654 6.48668 17.3733 6.52198L17.3624 15.6645C17.3621 15.9404 17.1383 16.1639 16.8624 16.1639H15.0531C14.2455 16.1639 13.4571 16.3375 12.8559 16.7612L12.8531 16.7631V7.30168C12.8531 6.07105 13.8955 4.90955 15.0677 4.90955H16.8746C17.151 4.90955 17.3749 5.13377 17.3746 5.41014L17.3736 6.30145ZM11.8531 7.30168C11.8531 6.06371 10.8333 4.90955 9.66773 4.90955H7.86862C7.59271 4.90955 7.36895 5.13304 7.36862 5.40895L7.35642 15.6633C7.35609 15.9397 7.58005 16.1639 7.85642 16.1639H9.65314C10.4608 16.1639 11.2492 16.3375 11.8504 16.7612L11.8531 16.7631V7.30168Z" fill="#212529"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.2 -0.3 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M6.9.7c.5-1 1.3-1 1.8 0l6.7 12.9c.5 1 0 1.8-1.1 1.8h-13c-1.1 0-1.6-.8-1.1-1.8L6.9.7z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.2 -0.2 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M14.7 6.9c1 .5 1 1.3 0 1.8L1.8 15.4c-1 .5-1.8 0-1.8-1.1v-13C0 .2.8-.3 1.8.2l12.9 6.7z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -0.2 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M15 6.9L1.9.2C.9-.3 0 .2 0 1.3v13c0 1.1.9 1.6 1.9 1.1L15 8.7c.9-.5 1-1.3 0-1.8zm-4.4 1.8l-6.7 3.4c-1 .5-1.9 0-1.9-1.1V4.6c0-1.1.9-1.6 1.9-1.1l6.6 3.4c1 .5 1.1 1.3.1 1.8z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.3 -0.2 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M.7 6.9c-1 .5-1 1.3 0 1.8l12.9 6.7c1 .5 1.8 0 1.8-1.1v-13c0-1.1-.8-1.6-1.8-1.1L.7 6.9z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

+3
View File
@@ -0,0 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.3 -0.2 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M13.8.2L.7 6.9c-1 .5-1 1.3 0 1.8l13.1 6.7c1 .5 1.9 0 1.9-1.1v-13c0-1.1-.9-1.6-1.9-1.1zM13.7 13L3.6 7.8l10.1-5.2V13z" fill="#888"/></svg>

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512" preserveAspectRatio="xMinYMid meet"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"/></svg>

After

Width:  |  Height:  |  Size: 399 B

+46
View File
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book plugin settings
*
* @package mod_book
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
require_once(__DIR__.'/lib.php');
// General settings
$options = book_get_numbering_types();
$settings->add(new admin_setting_configmultiselect('book/numberingoptions',
get_string('numberingoptions', 'mod_book'), get_string('numberingoptions_desc', 'mod_book'),
array_keys($options), $options));
// Modedit defaults.
$settings->add(new admin_setting_heading('bookmodeditdefaults',
get_string('modeditdefaults', 'admin'), get_string('condifmodeditdefaults', 'admin')));
$settings->add(new admin_setting_configselect('book/numbering',
get_string('numbering', 'mod_book'), '', BOOK_NUM_NUMBERS, $options));
}
+76
View File
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Show/hide book chapter
*
* @package mod_book
* @copyright 2004-2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$id = required_param('id', PARAM_INT); // Course Module ID
$chapterid = required_param('chapterid', PARAM_INT); // Chapter ID
$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
require_sesskey();
$context = context_module::instance($cm->id);
require_capability('mod/book:edit', $context);
$PAGE->set_url('/mod/book/show.php', array('id'=>$id, 'chapterid'=>$chapterid));
$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
// Switch hidden state.
$chapter->hidden = $chapter->hidden ? 0 : 1;
// Update record.
$DB->update_record('book_chapters', $chapter);
\mod_book\event\chapter_updated::create_from_chapter($book, $context, $chapter)->trigger();
// Change visibility of subchapters too.
if (!$chapter->subchapter) {
$chapters = $DB->get_recordset('book_chapters', array('bookid'=>$book->id), 'pagenum ASC');
$found = 0;
foreach ($chapters as $ch) {
if ($ch->id == $chapter->id) {
$found = 1;
} else if ($found and $ch->subchapter) {
$ch->hidden = $chapter->hidden;
$DB->update_record('book_chapters', $ch);
\mod_book\event\chapter_updated::create_from_chapter($book, $context, $ch)->trigger();
} else if ($found) {
break;
}
}
$chapters->close();
}
book_preload_chapters($book); // fix structure
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
redirect('view.php?id='.$cm->id.'&chapterid='.$chapter->id);
+118
View File
@@ -0,0 +1,118 @@
/* == Fake toc block == */
.path-mod-book .book_toc .action-list img.smallicon {
margin: 0 3px;
}
/* toc style INDENTED*/
.path-mod-book .book_toc ul {
display: flex;
flex-direction: column;
}
.path-mod-book .book_toc li {
flex: 1 1 100%;
}
.path-mod-book .book_toc_indented > ul {
margin-left: 0;
padding-left: 0;
}
.path-mod-book .book_toc_indented li {
list-style: none;
}
/* toc style BULLETED*/
.path-mod-book .book_toc_bullets > ul {
margin-left: 0;
padding-left: 1.3rem;
}
.path-mod-book .book_toc_bullets li {
list-style: disc;
}
/* toc style NUMBERED*/
.path-mod-book .book_toc_numbered > ul {
margin-left: 0;
padding-left: 0;
}
.path-mod-book .book_toc_numbered li {
list-style: none;
}
/* toc style NONE*/
.path-mod-book .book_toc_none ul {
margin-left: 0;
padding-left: 0;
}
.path-mod-book .book_toc_none li {
list-style: none;
}
.path-mod-book #mod_book-chaptersnavigation {
top: 80px;
width: inherit;
margin: 0 -35px;
position: sticky; /* Make navigation sticky. */
}
.path-mod-book #mod_book-chaptersnavigation .tertiary-navigation .navitem {
margin-bottom: -52px;
}
.path-mod-book .book_content {
margin: 0 5px;
padding-right: 15px;
padding-left: 15px;
position: relative; /* Chapter navigation should not float on top of content. */
}
.path-mod-book .btn-previous,
.path-mod-book .btn-next {
background: #dee2e6;
box-shadow: 0 0.125rem 0.25rem rgb(0 0 0 / 0.075);
padding: 13px 10px;
}
.path-mod-book .btn-previous {
border-radius: 200px 0 0 200px;
}
.path-mod-book .btn-next {
border-radius: 0 200px 200px 0;
}
.path-mod-book.dir-rtl .btn-previous .icon,
.path-mod-book.dir-rtl .btn-next .icon {
transform: scale(-1, -1); /* Flip the icons in RTL languages */
}
.path-mod-book .btn-previous .icon,
.path-mod-book .btn-next .icon {
font-size: 24px;
width: auto;
height: auto;
margin: auto;
}
@media (max-width: 768px) {
.path-mod-book #mod_book-chaptersnavigation {
top: calc(100% - 250px);
margin: 0 -0.5rem;
z-index: 1;
}
.path-mod-book .btn-previous,
.path-mod-book .btn-next {
opacity: 0.85;
border-radius: 25px;
padding: 10px;
width: 45px;
height: 45px;
}
.path-mod-book .book_content {
margin-left: -5px;
margin-right: -5px;
}
}
@@ -0,0 +1,58 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template mod_book/main_action_menu
Previous and next buttons for the book.
Classes required for JS:
* none
Context variables required for this template:
* see mod/book/classes/output/main_actionbar.php
Example context (json):
{
"previous": {
"url": "https://moodle.org",
"title": "Chapter 1: First of many"
},
"next": {
"url": "https://moodle.org",
"title": "Chapter 3: Last of many"
}
}
}}
<div class="container-fluid tertiary-navigation">
<div class="row">
{{#previous}}
<div class="navitem">
<a class="btn btn-link btn-previous" href="{{url}}#mod_book-chapter" title="{{title}}">
{{#pix}} i/previous, core {{/pix}}
</a>
</div>
{{/previous}}
{{#next}}
<div class="navitem ml-auto">
<a class="btn btn-link btn-next" href="{{url}}#mod_book-chapter" title="{{title}}">
{{#pix}} i/next, core {{/pix}}
</a>
</div>
{{/next}}
</div>
</div>
@@ -0,0 +1,84 @@
@mod @mod_book @core_completion
Feature: View activity completion information in the book activity
In order to have visibility of book completion requirements
As a student
I need to be able to view my book completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | enablecompletion | showcompletionconditions |
| Course 1 | C1 | 1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activity" exists:
| activity | book |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| completion | 2 |
| completionview | 1 |
Scenario: View automatic completion items
Given the following "mod_book > chapter" exists:
| book | Music history |
| title | Drum theory |
| content | Rudiments are important |
And I am on the "Music history" "book activity" page logged in as teacher1
And "Music history" should have the "View" completion condition
# Student view.
When I am on the "Music history" "book activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
Scenario: View automatic completion items with last section hidden
Given the following "activity" exists:
| activity | book |
| course | C1 |
| idnumber | arth1 |
| name | Art history |
| completion | 2 |
| completionview | 1 |
And the following "mod_book > chapters" exist:
| book | title | content | pagenum | subchapter | hidden |
| Art history | First chapter | First chapter | 1 | 0 | 0 |
| Art history | Second chapter | Second chapter | 2 | 0 | 0 |
| Art history | Sub chapter 1 | Sub chapter | 3 | 1 | 0 |
| Art history | Sub chapter 2 | Sub chapter | 4 | 1 | 0 |
| Art history | Sub chapter 3 | Sub chapter | 5 | 1 | 1 |
When I am on the "Art history" "book activity" page logged in as student1
And I should see "First chapter"
And the "View" completion condition of "Art history" is displayed as "todo"
And I follow "Next"
And I should see "Second chapter"
And the "View" completion condition of "Art history" is displayed as "todo"
And I follow "Next"
And I should see "Sub chapter 1"
And the "View" completion condition of "Art history" is displayed as "todo"
And I follow "Next"
And I should see "Sub chapter 2"
And I should not see "Next"
Then the "View" completion condition of "Art history" is displayed as "done"
@javascript
Scenario: Use manual completion
Given I am on the "Music history" "book activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the field "Students must manually mark the activity as done" to "1"
And I press "Save and display"
And I set the following fields to these values:
| Chapter title | Drum theory |
| Content | Rudiments are important |
And I press "Save changes"
And I am on the "Music history" "book activity" page
# Teacher view.
And the manual completion button for "Music history" should be disabled
# Student view.
Given I am on the "Music history" "book activity" page logged in as student1
Then the manual completion button of "Music history" is displayed as "Mark as done"
And I toggle the manual completion state of "Music history"
And the manual completion button of "Music history" is displayed as "Done"
@@ -0,0 +1,88 @@
@mod @mod_book
Feature: In a book, create chapters and sub chapters
In order to create chapters and subchapters
As a teacher
I need to add chapters and subchapters to a book.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| format | topics |
| numsections | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | course | section |
| book | Test book | C1 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
Scenario: Create chapters and sub chapters and navigate between them
Given the following "mod_book > chapter" exists:
| book | Test book |
| title | Dummy first chapter |
| content | Dream is the start of a journey |
And I am on the "Test book" "book activity" page
And I should not see "No content has been added to this book yet."
And I should see "1. Dummy first chapter" in the "Table of contents" "block"
And I click on "Add new chapter after \"Dummy first chapter\"" "link" in the "Table of contents" "block"
And I should see "Dummy first chapter"
And I set the following fields to these values:
| Chapter title | Dummy second chapter |
| Content | The path is the second part |
And I press "Save changes"
And I should see "2. Dummy second chapter" in the "Table of contents" "block"
And I click on "Add new chapter after \"Dummy first chapter\"" "link" in the "Table of contents" "block"
And I should see "Dummy first chapter"
And I set the following fields to these values:
| Chapter title | Dummy first subchapter |
| Content | The path is the second part |
| Subchapter | true |
And I press "Save changes"
And I should see "1.1. Dummy first subchapter" in the "Table of contents" "block"
And I should see "1. Dummy first chapter" in the ".book_content" "css_element"
And I should see "1.1. Dummy first subchapter" in the ".book_content" "css_element"
And I click on "Next" "link"
And I should see "2. Dummy second chapter" in the ".book_content" "css_element"
And I should see "2. Dummy second chapter" in the "strong" "css_element"
And I should not see "Next" in the ".book_content" "css_element"
And I am on "Course 1" course homepage
And I should see "Test book" in the "New section" "section"
And I click on "Test book" "link" in the "New section" "section"
And I should not see "Previous" in the ".book_content" "css_element"
And I should see "1. Dummy first chapter" in the "strong" "css_element"
When I click on "Next" "link"
Then I should see "1.1. Dummy first subchapter" in the ".book_content" "css_element"
And I should see "1.1. Dummy first subchapter" in the "strong" "css_element"
And I click on "Previous" "link"
And I should see "1. Dummy first chapter" in the ".book_content" "css_element"
And I should see "1. Dummy first chapter" in the "strong" "css_element"
Scenario: Change editing mode for an individual chapter
Given the following "mod_book > chapter" exists:
| book | Test book |
| title | Dummy first chapter |
| content | Dream is the start of a journey |
And I am on the "Test book" "book activity" page
And I should see "1. Dummy first chapter" in the "Table of contents" "block"
And "Edit chapter \"1. Dummy first chapter\"" "link" should exist in the "Table of contents" "block"
And "Delete chapter \"1. Dummy first chapter\"" "link" should exist in the "Table of contents" "block"
And "Hide chapter \"1. Dummy first chapter\"" "link" should exist in the "Table of contents" "block"
And "Add new chapter" "link" should exist in the "Table of contents" "block"
When I turn editing mode off
Then "Edit chapter \"1. Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
And "Delete chapter \"1. Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
And "Hide chapter \"1. Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
And "Add new chapter after \"Dummy first chapter\"" "link" should not exist in the "Table of contents" "block"
Scenario: When chapters are not created yet, students can see a notification in the book activity
Given I am on the "Test book" "book activity" page logged in as student1
Then I should see "No content has been added to this book yet." in the ".alert-info" "css_element"
And I should not see "Table of contents"
@@ -0,0 +1,55 @@
@mod @mod_book
Feature: Display the book description in the book and optionally in the course
In order to display the the book description in the course
As a teacher
I need to enable the 'Display description on course page' setting.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| book | Test book | A book about dreams! | C1 | book1 |
And the following "mod_book > chapter" exists:
| book | Test book |
| title | Dummy first chapter |
| content | Dream is the start of a journey |
Scenario: Description is displayed in the book
When I am on the "Test book" "book activity" page logged in as teacher1
Then I should see "A book about dreams!"
Scenario: Show book description in the course homepage
Given I am on the "Test book" "book activity editing" page logged in as teacher1
And the following fields match these values:
| Display description on course page | |
And I set the following fields to these values:
| Display description on course page | 1 |
When I press "Save and return to course"
Then I should see "A book about dreams!"
Scenario: Hide book description in the course homepage
Given I am on the "Test book" "book activity editing" page logged in as teacher1
And the following fields match these values:
| Display description on course page | |
When I press "Save and return to course"
Then I should not see "A book about dreams!"
@javascript
Scenario: Description is displayed in the book for students when there are no chapters added yet
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I am on the "Test book" "book activity" page
And I click on "Delete chapter \"1. Dummy first chapter\"" "link" in the "Table of contents" "block"
And I click on "Yes" "button" in the "Confirmation" "dialogue"
And I am on the "Test book" "book activity" page logged in as student1
Then I should see "A book about dreams!"
@@ -0,0 +1,43 @@
@mod @mod_book
Feature: In a book, change the navigation options
In order to change the way a book's chapters can be traversed
As a teacher
I need to change navigation options on a book
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
# The option to "Style of navigation" is removed from the settings.
Scenario: Change navigation options
Given the following "activities" exist:
| activity | name | course | idnumber | navstyle |
| book | Test book | C1 | book1 | 0 |
And I am on "Course 1" course homepage with editing mode on
And I follow "Test book"
And I should see "Add new chapter"
And I set the following fields to these values:
| Chapter title | Test chapter 1 |
| Content | Lorem ipsum dolor sit amet |
And I press "Save changes"
And I should see "Test book"
And I should see "1. Test chapter 1"
And I click on "Add new chapter" "link" in the "Table of contents" "block"
And I set the following fields to these values:
| Chapter title | Test chapter 2 |
| Content | consectetur adipiscing elit |
And I press "Save changes"
And I should see "Test book"
And I should see "2. Test chapter 2"
And I click on "1. Test chapter 1" "link" in the "Table of contents" "block"
And "Next" "link" should exist
And I click on "2. Test chapter 2" "link" in the "Table of contents" "block"
And "Previous" "link" should exist
+65
View File
@@ -0,0 +1,65 @@
@mod @mod_book @core_tag @javascript
Feature: Edited book chapters handle tags correctly
In order to get book chapters properly labelled
As a user
I need to introduce the tags while editing
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "activity" exists:
| activity | book |
| course | C1 |
| idnumber | book1 |
| name | Test book |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Scenario: Book chapter edition of custom tags works as expected
Given I am on the "Test book" "book activity" page logged in as teacher1
And I set the following fields to these values:
| Chapter title | Dummy first chapter |
| Content | Dream is the start of a journey |
| Tags | Example, Chapter, Cool |
And I press "Save changes"
Then I should see "Example" in the ".book-tags" "css_element"
And I should see "Chapter" in the ".book-tags" "css_element"
And I should see "Cool" in the ".book-tags" "css_element"
And I turn editing mode on
And I follow "Edit chapter \"1. Dummy first chapter\""
Then I should see "Example" in the ".form-autocomplete-selection" "css_element"
Then I should see "Chapter" in the ".form-autocomplete-selection" "css_element"
Then I should see "Cool" in the ".form-autocomplete-selection" "css_element"
@javascript
Scenario: Book chapter edition of standard tags works as expected
Given the following "tags" exist:
| name | isstandard |
| OT1 | 1 |
| OT2 | 1 |
| OT3 | 1 |
And I am on the "Test book" "book activity" page logged in as teacher1
And I open the autocomplete suggestions list
And I should see "OT1" in the ".form-autocomplete-suggestions" "css_element"
And I should see "OT2" in the ".form-autocomplete-suggestions" "css_element"
And I should see "OT3" in the ".form-autocomplete-suggestions" "css_element"
When I set the following fields to these values:
| Chapter title | Dummy first chapter |
| Content | Dream is the start of a journey |
| Tags | OT1, OT3 |
And I press "Save changes"
Then I should see "OT1" in the ".book-tags" "css_element"
And I should see "OT3" in the ".book-tags" "css_element"
And I should not see "OT2" in the ".book-tags" "css_element"
And I turn editing mode on
And I follow "Edit chapter \"1. Dummy first chapter\""
And I should see "OT1" in the ".form-autocomplete-selection" "css_element"
And I should see "OT3" in the ".form-autocomplete-selection" "css_element"
And I should not see "OT2" in the ".form-autocomplete-selection" "css_element"
+69
View File
@@ -0,0 +1,69 @@
@mod @mod_book
Feature: In a book, verify log entries
In order to create log entries
As an admin
I need to perform various actions in a book.
@javascript @_switch_window
Scenario: perform various book actions and verify log entries.
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "activity" exists:
| course | C1 |
| activity | book |
| name | Test book |
And I am on the "Course 1" course page logged in as admin
And I turn editing mode on
And I am on the "Test book" "book activity" page
And I set the following fields to these values:
| Chapter title | First chapter |
| Content | First chapter |
And I press "Save changes"
And I click on "Add new chapter" "link" in the "Table of contents" "block"
And I set the following fields to these values:
| Chapter title | Second chapter |
| Content | Second chapter |
And I press "Save changes"
And I click on "Edit" "link" in the "Table of contents" "block"
And I set the following fields to these values:
| Chapter title | First chapter edited |
| Content | First chapter edited |
And I press "Save changes"
And I click on "Next" "link"
And I click on "Previous" "link"
And I navigate to "Print book" in current page administration
And I am on the "Test book" "book activity" page
And I navigate to "Download IMS CP" in current page administration
And I navigate to "Reports > Logs" in site administration
And I set the field "menuid" to "Course 1"
And I press "Get these logs"
Then I should see "Book exported"
And I should see "Book printed"
And I should see "Chapter viewed" in the "#report_log_r4_c5" "css_element"
And I should see "Chapter viewed" in the "#report_log_r5_c5" "css_element"
And I should see "Chapter viewed" in the "#report_log_r6_c5" "css_element"
And I should see "Chapter updated" in the "#report_log_r7_c5" "css_element"
And I should see "Chapter viewed" in the "#report_log_r8_c5" "css_element"
And I should see "Chapter created" in the "#report_log_r9_c5" "css_element"
And I click on "Chapter viewed" "link" in the "#report_log_r4_c5" "css_element"
And I switch to "action" window
And I change window size to "large"
And I should see "1. First chapter edited" in the ".book_content" "css_element"
And I switch to the main window
And I click on "Chapter viewed" "link" in the "#report_log_r5_c5" "css_element"
And I switch to "action" window
And I should see "2. Second chapter" in the ".book_content" "css_element"
And I switch to the main window
And I click on "Chapter updated" "link" in the "#report_log_r7_c5" "css_element"
And I switch to "action" window
And I should see "1. First chapter edited" in the ".book_content" "css_element"
And I switch to the main window
And I click on "Chapter created" "link" in the "#report_log_r9_c5" "css_element"
And I switch to "action" window
And I should see "2. Second chapter" in the ".book_content" "css_element"
And I switch to the main window
And I click on "Chapter created" "link" in the "#report_log_r11_c5" "css_element"
And I switch to "action" window
And I should see "1. First chapter edited" in the ".book_content" "css_element"
And I switch to the main window
@@ -0,0 +1,75 @@
@mod @mod_book
Feature: In a book, chapters and subchapters can be rearranged
In order to rearrange chapters and subchapters
As a teacher
I need to move chapters and subchapters up and down.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | course | idnumber |
| book | Test book | C1 | book1 |
And the following "mod_book > chapters" exist:
| book | title | content | pagenum |subchapter |
| Test book | Originally first chapter | #1 chapter content | 1 | 0 |
| Test book | A great second chapter | #2 chapter content | 2 | 0 |
| Test book | Second chapter, subchapter 1 | #21 subchapter content | 3 | 1 |
| Test book | Second chapter, subchapter 2 | #22 subchapter content | 4 | 1 |
| Test book | There aren't 2 without 3 | #3 subchapter content | 5 | 0 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "Test book"
Scenario: Moving chapters down rearranges them properly
Given I click on "Move chapter down \"1. Originally first chapter\"" "link"
When I am on the "Test book" "book activity" page
Then I should see "1. A great second chapter"
And I should see "#2 chapter content"
And I should see "1.1. Second chapter, subchapter 1"
And I should see "1.2. Second chapter, subchapter 2"
And I should see "2. Originally first chapter"
And I should see "3. There aren't 2 without 3"
Scenario: Moving chapters up rearranges them properly
Given I click on "Move chapter up \"3. There aren't 2 without 3\"" "link"
When I am on the "Test book" "book activity" page
Then I should see "1. Originally first chapter"
And I should see "#1 chapter content"
And I should see "2. There aren't 2 without 3"
And I should see "3. A great second chapter"
And I should see "3.1. Second chapter, subchapter 1"
And I should see "3.2. Second chapter, subchapter 2"
Scenario: Moving subchapters down within chapter rearranges them properly
Given I click on "Move chapter down \"2.1. Second chapter, subchapter 1\"" "link"
When I should see "2.1. Second chapter, subchapter 2"
Then I should see "2.2. Second chapter, subchapter 1"
Scenario: Moving subchapters down out of chapter rearranges them properly
Given I click on "Move chapter down \"2.2. Second chapter, subchapter 2\"" "link"
When I should see "3.1. Second chapter, subchapter 2"
Then I click on "Move chapter down \"3. There aren't 2 without 3\"" "link"
And I should not see "4. There aren't 2 without 3"
And I should see "3. There aren't 2 without 3"
And I should see "3.1. Second chapter, subchapter 2"
Scenario: Moving subchapters up within chapter rearranges them properly
Given I click on "Move chapter up \"2.2. Second chapter, subchapter 2\"" "link"
When I should see "2.1. Second chapter, subchapter 2"
Then I should see "2.2. Second chapter, subchapter 1"
Scenario: Moving subchapters up out of chapter rearranges them properly
Given I click on "Move chapter up \"2.1. Second chapter, subchapter 1\"" "link"
When I should see "1.1. Second chapter, subchapter 1"
Then I click on "Move chapter up \"1.1. Second chapter, subchapter 1\"" "link"
And I should not see "1.1. Second chapter, subchapter 1"
And I should see "1. Second chapter, subchapter 1"
And I should see "2. Originally first chapter"
@@ -0,0 +1,59 @@
@mod @mod_book
Feature: Book activity chapter visibility management
In order to properly manage chapters in a book activity
As a teacher
I need to be able to show or hide chapters.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 2 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activity" exists:
| course | C1 |
| activity | book |
| name | Test book |
And the following "mod_book > chapters" exist:
| book | title | content | pagenum |subchapter |
| Test book | First chapter | First chapter | 1 | 0 |
| Test book | Second chapter | Second chapter | 2 | 0 |
| Test book | Sub chapter | Sub chapter | 3 | 1 |
| Test book | Third chapter | Third chapter | 4 | 0 |
| Test book | Fourth chapter | Fourth chapter | 5 | 0 |
And I am on the "Course 1" course page logged in as teacher1
And I turn editing mode on
And I am on the "Test book" "book activity" page
And I click on "4. Fourth chapter" "link" in the "Table of contents" "block"
@javascript
Scenario: Show/hide chapters and subchapters
When I follow "Hide chapter \"2. Second chapter\""
And I follow "Hide chapter \"2. Third chapter\""
And I am on the "Test book" "book activity" page
And I am on "Course 1" course homepage
And I turn editing mode off
And I click on "Test book" "link" in the "region-main" "region"
Then the "class" attribute of "a[title='Second chapter']" "css_element" should contain "dimmed_text"
And the "class" attribute of "a[title='Third chapter']" "css_element" should contain "dimmed_text"
And I am on "Course 1" course homepage with editing mode on
And I click on "Test book" "link" in the "region-main" "region"
And I follow "Next"
And I should see "Second chapter" in the ".book_content" "css_element"
And I follow "Next"
And I should see "Sub chapter" in the ".book_content" "css_element"
And I follow "Next"
And I should see "Third chapter" in the ".book_content" "css_element"
And I follow "Next"
And I should see "Fourth chapter" in the ".book_content" "css_element"
And I am on the "Test book" "book activity" page logged in as student1
And I should not see "Second chapter" in the "Table of contents" "block"
And I should not see "Third chapter" in the "Table of contents" "block"
And I follow "Next"
And I should see "Fourth chapter" in the ".book_content" "css_element"
+196
View File
@@ -0,0 +1,196 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Events tests.
*
* @package mod_book
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\event;
/**
* Events tests class.
*
* @package mod_book
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
public function test_chapter_created(): void {
// There is no proper API to call to generate chapters for a book, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$context = \context_module::instance($book->cmid);
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$event = \mod_book\event\chapter_created::create_from_chapter($book, $context, $chapter);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\chapter_created', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($chapter->id, $event->objectid);
}
public function test_chapter_updated(): void {
// There is no proper API to call to generate chapters for a book, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$context = \context_module::instance($book->cmid);
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$event = \mod_book\event\chapter_updated::create_from_chapter($book, $context, $chapter);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\chapter_updated', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($chapter->id, $event->objectid);
}
public function test_chapter_deleted(): void {
// There is no proper API to call to delete chapters for a book, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$context = \context_module::instance($book->cmid);
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$event = \mod_book\event\chapter_deleted::create_from_chapter($book, $context, $chapter);
$legacy = array($course->id, 'book', 'update', 'view.php?id='.$book->cmid, $book->id, $book->cmid);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\chapter_deleted', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($chapter->id, $event->objectid);
$this->assertEquals($chapter, $event->get_record_snapshot('book_chapters', $chapter->id));
}
public function test_course_module_instance_list_viewed(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$params = array(
'context' => \context_course::instance($course->id)
);
$event = \mod_book\event\course_module_instance_list_viewed::create($params);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\course_module_instance_list_viewed', $event);
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
}
public function test_course_module_viewed(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$params = array(
'context' => \context_module::instance($book->cmid),
'objectid' => $book->id
);
$event = \mod_book\event\course_module_viewed::create($params);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\course_module_viewed', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($book->id, $event->objectid);
}
public function test_chapter_viewed(): void {
// There is no proper API to call to trigger this event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$context = \context_module::instance($book->cmid);
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$event = \mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\chapter_viewed', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($chapter->id, $event->objectid);
}
}
+198
View File
@@ -0,0 +1,198 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_book;
use core_external\external_api;
use externallib_advanced_testcase;
use mod_book_external;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External mod_book functions unit tests
*
* @package mod_book
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Test view_book
*/
public function test_view_book(): void {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$chapterhidden = $bookgenerator->create_chapter(array('bookid' => $book->id, 'hidden' => 1));
$context = \context_module::instance($book->cmid);
$cm = get_coursemodule_from_instance('book', $book->id);
// Test invalid instance id.
try {
mod_book_external::view_book(0);
$this->fail('Exception expected due to invalid mod_book instance id.');
} catch (\moodle_exception $e) {
$this->assertEquals('invalidrecord', $e->errorcode);
}
// Test not-enrolled user.
$user = self::getDataGenerator()->create_user();
$this->setUser($user);
try {
mod_book_external::view_book($book->id, 0);
$this->fail('Exception expected due to not enrolled user.');
} catch (\moodle_exception $e) {
$this->assertEquals('requireloginerror', $e->errorcode);
}
// Test user with full capabilities.
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Trigger and capture the event.
$sink = $this->redirectEvents();
$result = mod_book_external::view_book($book->id, 0);
$result = external_api::clean_returnvalue(mod_book_external::view_book_returns(), $result);
$events = $sink->get_events();
$this->assertCount(2, $events);
$event = array_shift($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\course_module_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodleurl = new \moodle_url('/mod/book/view.php', array('id' => $cm->id));
$this->assertEquals($moodleurl, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
$event = array_shift($events);
$this->assertInstanceOf('\mod_book\event\chapter_viewed', $event);
$this->assertEquals($chapter->id, $event->objectid);
$result = mod_book_external::view_book($book->id, $chapter->id);
$result = external_api::clean_returnvalue(mod_book_external::view_book_returns(), $result);
$events = $sink->get_events();
// We expect a total of 3 events.
$this->assertCount(3, $events);
// Try to view a hidden chapter.
try {
mod_book_external::view_book($book->id, $chapterhidden->id);
$this->fail('Exception expected due to missing capability.');
} catch (\moodle_exception $e) {
$this->assertEquals('errorchapter', $e->errorcode);
}
// Test user with no capabilities.
// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
assign_capability('mod/book:read', CAP_PROHIBIT, $studentrole->id, $context->id);
accesslib_clear_all_caches_for_unit_testing();
try {
mod_book_external::view_book($book->id, 0);
$this->fail('Exception expected due to missing capability.');
} catch (\moodle_exception $e) {
$this->assertEquals('nopermissions', $e->errorcode);
}
}
/**
* Test get_books_by_courses
*/
public function test_get_books_by_courses(): void {
global $DB, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$course1 = self::getDataGenerator()->create_course();
$bookoptions1 = array(
'course' => $course1->id,
'name' => 'First Book'
);
$book1 = self::getDataGenerator()->create_module('book', $bookoptions1);
$course2 = self::getDataGenerator()->create_course();
$bookoptions2 = array(
'course' => $course2->id,
'name' => 'Second Book'
);
$book2 = self::getDataGenerator()->create_module('book', $bookoptions2);
$student1 = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
// Enroll Student1 in Course1.
self::getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id);
$this->setUser($student1);
$books = mod_book_external::get_books_by_courses();
// We need to execute the return values cleaning process to simulate the web service server.
$books = external_api::clean_returnvalue(mod_book_external::get_books_by_courses_returns(), $books);
$this->assertCount(1, $books['books']);
$this->assertEquals('First Book', $books['books'][0]['name']);
// We see 10 fields.
$this->assertCount(11, $books['books'][0]);
// As Student you cannot see some book properties like 'section'.
$this->assertFalse(isset($books['books'][0]['section']));
// Student1 is not enrolled in course2. The webservice will return a warning!
$books = mod_book_external::get_books_by_courses(array($course2->id));
// We need to execute the return values cleaning process to simulate the web service server.
$books = external_api::clean_returnvalue(mod_book_external::get_books_by_courses_returns(), $books);
$this->assertCount(0, $books['books']);
$this->assertEquals(1, $books['warnings'][0]['warningcode']);
// Now as admin.
$this->setAdminUser();
// As Admin we can see this book.
$books = mod_book_external::get_books_by_courses(array($course2->id));
// We need to execute the return values cleaning process to simulate the web service server.
$books = external_api::clean_returnvalue(mod_book_external::get_books_by_courses_returns(), $books);
$this->assertCount(1, $books['books']);
$this->assertEquals('Second Book', $books['books'][0]['name']);
// We see 17 fields.
$this->assertCount(18, $books['books'][0]);
// As an Admin you can see some book properties like 'section'.
$this->assertEquals(0, $books['books'][0]['section']);
// Enrol student in the second course.
self::getDataGenerator()->enrol_user($student1->id, $course2->id, $studentrole->id);
$this->setUser($student1);
$books = mod_book_external::get_books_by_courses();
$books = external_api::clean_returnvalue(mod_book_external::get_books_by_courses_returns(), $books);
$this->assertCount(2, $books['books']);
}
}
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Behat data generator for mod_book.
*
* @package mod_book
* @category test
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Behat data generator for mod_book.
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_book_generator extends behat_generator_base {
protected function get_creatable_entities(): array {
return [
'chapters' => [
'singular' => 'chapter',
'datagenerator' => 'chapter',
'required' => ['book', 'title', 'content'],
'switchids' => ['book' => 'bookid'],
],
];
}
/**
* Look up the id of a book from its name.
*
* @param string $bookname the book name, for example 'Test book'.
* @return int corresponding id.
*/
protected function get_book_id(string $bookname): int {
global $DB;
$cm = $this->get_cm_by_activity_name('book', $bookname);
return $cm->instance;
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* mod_book data generator.
*
* @package mod_book
* @category test
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* mod_book data generator class.
*
* @package mod_book
* @category test
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_book_generator extends testing_module_generator {
/**
* @var int keep track of how many chapters have been created.
*/
protected $chaptercount = 0;
/**
* To be called from data reset code only,
* do not use in tests.
* @return void
*/
public function reset() {
$this->chaptercount = 0;
parent::reset();
}
public function create_instance($record = null, array $options = null) {
global $CFG;
require_once("$CFG->dirroot/mod/book/locallib.php");
$record = (object)(array)$record;
if (!isset($record->numbering)) {
$record->numbering = BOOK_NUM_NUMBERS;
}
if (!isset($record->customtitles)) {
$record->customtitles = 0;
}
return parent::create_instance($record, (array)$options);
}
public function create_chapter($record = null, array $options = null) {
global $DB;
$record = (object) (array) $record;
$options = (array) $options;
$this->chaptercount++;
if (empty($record->bookid)) {
throw new coding_exception('Chapter generator requires $record->bookid');
}
if (empty($record->title)) {
$record->title = "Chapter {$this->chaptercount}";
}
if (empty($record->pagenum)) {
$record->pagenum = 1;
}
if (!isset($record->subchapter)) {
$record->subchapter = 0;
}
if (!isset($record->hidden)) {
$record->hidden = 0;
}
if (!isset($record->importsrc)) {
$record->importsrc = '';
}
if (!isset($record->content)) {
$record->content = "Chapter {$this->chaptercount} content";
}
if (!isset($record->contentformat)) {
$record->contentformat = FORMAT_MOODLE;
}
if (!isset($record->timecreated)) {
$record->timecreated = time();
}
if (!isset($record->timemodified)) {
$record->timemodified = time();
}
// Make room for new page.
$sql = "UPDATE {book_chapters}
SET pagenum = pagenum + 1
WHERE bookid = ? AND pagenum >= ?";
$DB->execute($sql, array($record->bookid, $record->pagenum));
$record->id = $DB->insert_record('book_chapters', $record);
$sql = "UPDATE {book}
SET revision = revision + 1
WHERE id = ?";
$DB->execute($sql, array($record->bookid));
if (property_exists($record, 'tags')) {
$cm = get_coursemodule_from_instance('book', $record->bookid);
$tags = is_array($record->tags) ? $record->tags : preg_split('/,/', $record->tags);
core_tag_tag::set_item_tags('mod_book', 'book_chapters', $record->id,
context_module::instance($cm->id), $tags);
}
return $record;
}
public function create_content($instance, $record = array()) {
$record = (array)$record + array(
'bookid' => $instance->id
);
return $this->create_chapter($record);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_book;
/**
* Generator tests class.
*
* @package mod_book
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends \advanced_testcase {
public function test_create_instance(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$this->assertFalse($DB->record_exists('book', array('course' => $course->id)));
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$this->assertEquals(1, $DB->count_records('book', array('course' => $course->id)));
$this->assertTrue($DB->record_exists('book', array('course' => $course->id, 'id' => $book->id)));
$params = array('course' => $course->id, 'name' => 'One more book');
$book = $this->getDataGenerator()->create_module('book', $params);
$this->assertEquals(2, $DB->count_records('book', array('course' => $course->id)));
$this->assertEquals('One more book', $DB->get_field_select('book', 'name', 'id = :id', array('id' => $book->id)));
}
public function test_create_chapter(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$this->assertFalse($DB->record_exists('book_chapters', array('bookid' => $book->id)));
$bookgenerator->create_chapter(array('bookid' => $book->id));
$this->assertTrue($DB->record_exists('book_chapters', array('bookid' => $book->id)));
$chapter = $bookgenerator->create_chapter(
array('bookid' => $book->id, 'content' => 'Yay!', 'title' => 'Oops', 'tags' => array('Cats', 'mice')));
$this->assertEquals(2, $DB->count_records('book_chapters', array('bookid' => $book->id)));
$this->assertEquals('Oops', $DB->get_field_select('book_chapters', 'title', 'id = :id', array('id' => $chapter->id)));
$this->assertEquals('Yay!', $DB->get_field_select('book_chapters', 'content', 'id = :id', array('id' => $chapter->id)));
$this->assertEquals(array('Cats', 'mice'),
array_values(\core_tag_tag::get_item_tags_array('mod_book', 'book_chapters', $chapter->id)));
$chapter = $bookgenerator->create_content($book);
$this->assertEquals(3, $DB->count_records('book_chapters', array('bookid' => $book->id)));
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_book;
/**
* Helper test class
*
* @package mod_book
* @copyright 2023 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper_test extends \advanced_testcase {
/**
* Test view_book
* @covers \mod_book\helper::is_last_visible_chapter
*/
public function test_is_last_chapter(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$firstchapter =
$bookgenerator->create_chapter(array('bookid' => $book->id, 'pagenum' => 1)); // Create a first chapter to check that
// viewing the last chapter is enough for completing the activity.
$chapterhidden = $bookgenerator->create_chapter(array('bookid' => $book->id, 'hidden' => 1, 'pagenum' => 2));
$lastchapter = $bookgenerator->create_chapter(array('bookid' => $book->id, 'pagenum' => 3));
$chapters = book_preload_chapters($book);
$this->assertFalse(helper::is_last_visible_chapter($firstchapter->id, $chapters));
$this->assertFalse(helper::is_last_visible_chapter($chapterhidden->id, $chapters));
$this->assertTrue(helper::is_last_visible_chapter($lastchapter->id, $chapters));
}
}
+525
View File
@@ -0,0 +1,525 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for (some of) mod/book/lib.php.
*
* @package mod_book
* @category phpunit
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/book/lib.php');
/**
* Unit tests for (some of) mod/book/lib.php.
*
* @package mod_book
* @category phpunit
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
public function test_export_contents(): void {
global $DB, $CFG;
require_once($CFG->dirroot . '/course/externallib.php');
$user = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(array('enablecomment' => 1));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
// Test book with 3 chapters.
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$cm = get_coursemodule_from_id('book', $book->cmid);
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$chapter1 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 1,
'tags' => array('Cats', 'Dogs')));
$tag = \core_tag_tag::get_by_name(0, 'Cats');
$chapter2 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 2));
$subchapter = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 3, "subchapter" => 1));
$chapter3 = $bookgenerator->create_chapter(array('bookid' => $book->id, "pagenum" => 4, "hidden" => 1));
$this->setUser($user);
$contents = book_export_contents($cm, '');
// The hidden chapter must not be included, and additional page with the structure must be included.
$this->assertCount(4, $contents);
$this->assertEquals('structure', $contents[0]['filename']);
$this->assertEquals('index.html', $contents[1]['filename']);
$this->assertEquals('Chapter 1', $contents[1]['content']);
$this->assertCount(2, $contents[1]['tags']);
$this->assertEquals('Cats', $contents[1]['tags'][0]['rawname']);
$this->assertEquals($tag->id, $contents[1]['tags'][0]['id']);
$this->assertEquals('Dogs', $contents[1]['tags'][1]['rawname']);
$this->assertEquals('index.html', $contents[2]['filename']);
$this->assertEquals('Chapter 2', $contents[2]['content']);
$this->assertEquals('index.html', $contents[3]['filename']);
$this->assertEquals('Chapter 3', $contents[3]['content']);
// Now, test the function via the external API.
$contents = \core_course_external::get_course_contents($course->id, array());
$contents = external_api::clean_returnvalue(\core_course_external::get_course_contents_returns(), $contents);
$this->assertCount(4, $contents[0]['modules'][0]['contents']);
$this->assertEquals('content', $contents[0]['modules'][0]['contents'][0]['type']);
$this->assertEquals('structure', $contents[0]['modules'][0]['contents'][0]['filename']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][1]['type']);
$this->assertEquals('Chapter 1', $contents[0]['modules'][0]['contents'][1]['content']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][2]['type']);
$this->assertEquals('Chapter 2', $contents[0]['modules'][0]['contents'][2]['content']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][3]['type']);
$this->assertEquals('Chapter 3', $contents[0]['modules'][0]['contents'][3]['content']);
$this->assertEquals('book', $contents[0]['modules'][0]['modname']);
$this->assertEquals($cm->id, $contents[0]['modules'][0]['id']);
$this->assertCount(2, $contents[0]['modules'][0]['contents'][1]['tags']);
$this->assertEquals('Cats', $contents[0]['modules'][0]['contents'][1]['tags'][0]['rawname']);
$this->assertEquals('Dogs', $contents[0]['modules'][0]['contents'][1]['tags'][1]['rawname']);
// As a teacher.
$this->setUser($teacher);
$contents = book_export_contents($cm, '');
// As a teacher, the hidden chapter must be included in the structure.
$this->assertCount(5, $contents);
$this->assertEquals('structure', $contents[0]['filename']);
// Check structure is correct.
$foundhiddenchapter = false;
$chapters = json_decode($contents[0]['content']);
foreach ($chapters as $chapter) {
if ($chapter->title == 'Chapter 4' && $chapter->hidden == 1) {
$foundhiddenchapter = true;
}
}
$this->assertTrue($foundhiddenchapter);
$this->assertEquals('index.html', $contents[1]['filename']);
$this->assertEquals('Chapter 1', $contents[1]['content']);
$this->assertCount(2, $contents[1]['tags']);
$this->assertEquals('Cats', $contents[1]['tags'][0]['rawname']);
$this->assertEquals($tag->id, $contents[1]['tags'][0]['id']);
$this->assertEquals('Dogs', $contents[1]['tags'][1]['rawname']);
$this->assertEquals('index.html', $contents[2]['filename']);
$this->assertEquals('Chapter 2', $contents[2]['content']);
$this->assertEquals('index.html', $contents[3]['filename']);
$this->assertEquals('Chapter 3', $contents[3]['content']);
$this->assertEquals('index.html', $contents[4]['filename']);
$this->assertEquals('Chapter 4', $contents[4]['content']);
// Now, test the function via the external API.
$contents = \core_course_external::get_course_contents($course->id, array());
$contents = external_api::clean_returnvalue(\core_course_external::get_course_contents_returns(), $contents);
$this->assertCount(5, $contents[0]['modules'][0]['contents']);
$this->assertEquals('content', $contents[0]['modules'][0]['contents'][0]['type']);
$this->assertEquals('structure', $contents[0]['modules'][0]['contents'][0]['filename']);
// Check structure is correct.
$foundhiddenchapter = false;
$chapters = json_decode($contents[0]['modules'][0]['contents'][0]['content']);
foreach ($chapters as $chapter) {
if ($chapter->title == 'Chapter 4' && $chapter->hidden == 1) {
$foundhiddenchapter = true;
}
}
$this->assertTrue($foundhiddenchapter);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][1]['type']);
$this->assertEquals('Chapter 1', $contents[0]['modules'][0]['contents'][1]['content']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][2]['type']);
$this->assertEquals('Chapter 2', $contents[0]['modules'][0]['contents'][2]['content']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][3]['type']);
$this->assertEquals('Chapter 3', $contents[0]['modules'][0]['contents'][3]['content']);
$this->assertEquals('file', $contents[0]['modules'][0]['contents'][4]['type']);
$this->assertEquals('Chapter 4', $contents[0]['modules'][0]['contents'][4]['content']);
$this->assertEquals('book', $contents[0]['modules'][0]['modname']);
$this->assertEquals($cm->id, $contents[0]['modules'][0]['id']);
$this->assertCount(2, $contents[0]['modules'][0]['contents'][1]['tags']);
$this->assertEquals('Cats', $contents[0]['modules'][0]['contents'][1]['tags'][0]['rawname']);
$this->assertEquals('Dogs', $contents[0]['modules'][0]['contents'][1]['tags'][1]['rawname']);
// Test empty book.
$emptybook = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$cm = get_coursemodule_from_id('book', $emptybook->cmid);
$contents = book_export_contents($cm, '');
$this->assertCount(1, $contents);
$this->assertEquals('structure', $contents[0]['filename']);
$this->assertEquals(json_encode(array()), $contents[0]['content']);
}
/**
* Test book_view
* @return void
*/
public function test_book_view(): void {
global $CFG, $DB;
$CFG->enablecompletion = 1;
// Setup test data.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
$context = \context_module::instance($book->cmid);
$cm = get_coursemodule_from_instance('book', $book->id);
// Trigger and capture the event.
$sink = $this->redirectEvents();
// Check just opening the book.
book_view($book, 0, false, $course, $cm, $context);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = array_shift($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_book\event\course_module_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodleurl = new \moodle_url('/mod/book/view.php', array('id' => $cm->id));
$this->assertEquals($moodleurl, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
// Check viewing one book chapter (the only one so it will be the first and last).
book_view($book, $chapter, true, $course, $cm, $context);
$events = $sink->get_events();
// We expect a total of 4 events. One for module viewed, one for chapter viewed and two belonging to completion.
$this->assertCount(4, $events);
// Check completion status.
$completion = new \completion_info($course);
$completiondata = $completion->get_data($cm);
$this->assertEquals(1, $completiondata->completionstate);
}
public function test_book_core_calendar_provide_event_action(): void {
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_book_core_calendar_provide_event_action_in_hidden_section(): void {
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
// Enrol a student in the course.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Set sections 0 as hidden.
set_section_visible($course->id, 0, 0);
// Now, log out.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory, $student->id);
// Confirm the event is not shown at all.
$this->assertNull($actionevent);
}
public function test_book_core_calendar_provide_event_action_for_user(): void {
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
// Enrol a student in the course.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Now, log out.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory, $student->id);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_book_core_calendar_provide_event_action_as_non_user(): void {
global $CFG;
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Log out the user and set force login to true.
\core\session\manager::init_empty_session();
$CFG->forcelogin = true;
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory);
// Ensure result was null.
$this->assertNull($actionevent);
}
public function test_book_core_calendar_provide_event_action_already_completed(): void {
global $CFG;
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Get some additional data.
$cm = get_coursemodule_from_instance('book', $book->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory);
// Ensure result was null.
$this->assertNull($actionevent);
}
public function test_book_core_calendar_provide_event_action_already_completed_for_user(): void {
global $CFG;
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Enrol a student in the course.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Get some additional data.
$cm = get_coursemodule_from_instance('book', $book->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $book->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed for the student.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm, $student->id);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_book_core_calendar_provide_event_action($event, $factory, $student->id);
// Ensure result was null.
$this->assertNull($actionevent);
}
/**
* Creates an action event.
*
* @param int $courseid The course id.
* @param int $instanceid The instance id.
* @param string $eventtype The event type.
* @return bool|calendar_event
*/
private function create_action_event($courseid, $instanceid, $eventtype) {
$event = new \stdClass();
$event->name = 'Calendar event';
$event->modulename = 'book';
$event->courseid = $courseid;
$event->instance = $instanceid;
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->eventtype = $eventtype;
$event->timestart = time();
return \calendar_event::create($event);
}
public function test_mod_book_get_tagged_chapters(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$course3 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course1 = $this->getDataGenerator()->create_course();
$book1 = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));
$book2 = $this->getDataGenerator()->create_module('book', array('course' => $course2->id));
$book3 = $this->getDataGenerator()->create_module('book', array('course' => $course3->id));
$chapter11 = $bookgenerator->create_content($book1, array('tags' => array('Cats', 'Dogs')));
$chapter12 = $bookgenerator->create_content($book1, array('tags' => array('Cats', 'mice')));
$chapter13 = $bookgenerator->create_content($book1, array('tags' => array('Cats')));
$chapter14 = $bookgenerator->create_content($book1);
$chapter15 = $bookgenerator->create_content($book1, array('tags' => array('Cats')));
$chapter16 = $bookgenerator->create_content($book1, array('tags' => array('Cats'), 'hidden' => true));
$chapter21 = $bookgenerator->create_content($book2, array('tags' => array('Cats')));
$chapter22 = $bookgenerator->create_content($book2, array('tags' => array('Cats', 'Dogs')));
$chapter23 = $bookgenerator->create_content($book2, array('tags' => array('mice', 'Cats')));
$chapter31 = $bookgenerator->create_content($book3, array('tags' => array('mice', 'Cats')));
$tag = \core_tag_tag::get_by_name(0, 'Cats');
// Admin can see everything.
$res = mod_book_get_tagged_chapters($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$chapter = */0);
$this->assertMatchesRegularExpression('/'.$chapter11->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter12->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter13->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter14->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter15->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter16->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter21->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter22->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter23->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter31->title.'</', $res->content);
$this->assertEmpty($res->prevpageurl);
$this->assertNotEmpty($res->nextpageurl);
$res = mod_book_get_tagged_chapters($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$chapter = */1);
$this->assertDoesNotMatchRegularExpression('/'.$chapter11->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter12->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter13->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter14->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter15->title.'</', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter16->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter21->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter22->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter23->title.'</', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter31->title.'</', $res->content);
$this->assertNotEmpty($res->prevpageurl);
$this->assertEmpty($res->nextpageurl);
// Create and enrol a user.
$student = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
$this->getDataGenerator()->enrol_user($student->id, $course2->id, $studentrole->id, 'manual');
$this->setUser($student);
\core_tag_index_builder::reset_caches();
// User can not see chapters in course 3 because he is not enrolled.
$res = mod_book_get_tagged_chapters($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$chapter = */1);
$this->assertMatchesRegularExpression('/'.$chapter22->title.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter23->title.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter31->title.'/', $res->content);
// User can search book chapters inside a course.
$coursecontext = \context_course::instance($course1->id);
$res = mod_book_get_tagged_chapters($tag, /*$exclusivemode = */false,
/*$fromctx = */0, /*$ctx = */$coursecontext->id, /*$rec = */1, /*$chapter = */0);
$this->assertMatchesRegularExpression('/'.$chapter11->title.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter12->title.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter13->title.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter14->title.'/', $res->content);
$this->assertMatchesRegularExpression('/'.$chapter15->title.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter21->title.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter22->title.'/', $res->content);
$this->assertDoesNotMatchRegularExpression('/'.$chapter23->title.'/', $res->content);
$this->assertEmpty($res->nextpageurl);
// User cannot see hidden chapters.
$this->assertDoesNotMatchRegularExpression('/'.$chapter16->title.'/', $res->content);
}
}
+182
View File
@@ -0,0 +1,182 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book search unit tests.
*
* @package mod_book
* @category test
* @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_book\search;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
/**
* Provides the unit tests for book search.
*
* @package mod_book
* @category test
* @copyright 2016 Eric Merrill {@link http://www.merrilldigital.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class search_test extends \advanced_testcase {
/**
* @var string Area id
*/
protected $bookchapterareaid = null;
public function setUp(): void {
$this->resetAfterTest(true);
set_config('enableglobalsearch', true);
$this->bookchapterareaid = \core_search\manager::generate_areaid('mod_book', 'chapter');
// Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
$search = \testable_core_search::instance();
}
/**
* Availability.
*
* @return void
*/
public function test_search_enabled(): void {
$searcharea = \core_search\manager::get_search_area($this->bookchapterareaid);
list($componentname, $varname) = $searcharea->get_config_var_name();
// Enabled by default once global search is enabled.
$this->assertTrue($searcharea->is_enabled());
set_config($varname . '_enabled', 0, $componentname);
$this->assertFalse($searcharea->is_enabled());
set_config($varname . '_enabled', 1, $componentname);
$this->assertTrue($searcharea->is_enabled());
}
/**
* Indexing chapter contents.
*
* @return void
*/
public function test_chapters_indexing(): void {
global $DB;
// Returns the instance as long as the area is supported.
$searcharea = \core_search\manager::get_search_area($this->bookchapterareaid);
$this->assertInstanceOf('\mod_book\search\chapter', $searcharea);
$course1 = self::getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$chapter1 = $bookgenerator->create_chapter(array('bookid' => $book->id, 'content' => 'Chapter1', 'title' => 'Title1'));
$chapter2 = $bookgenerator->create_chapter(array('bookid' => $book->id, 'content' => 'Chapter2', 'title' => 'Title2'));
// All records.
$recordset = $searcharea->get_recordset_by_timestamp(0);
$this->assertTrue($recordset->valid());
$nrecords = 0;
foreach ($recordset as $record) {
$this->assertInstanceOf('stdClass', $record);
$doc = $searcharea->get_document($record);
$this->assertInstanceOf('\core_search\document', $doc);
// Static caches are working.
$dbreads = $DB->perf_get_reads();
$doc = $searcharea->get_document($record);
$this->assertEquals($dbreads, $DB->perf_get_reads());
$this->assertInstanceOf('\core_search\document', $doc);
$nrecords++;
}
// If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
$recordset->close();
$this->assertEquals(2, $nrecords);
// The +2 is to prevent race conditions.
$recordset = $searcharea->get_recordset_by_timestamp(time() + 2);
// No new records.
$this->assertFalse($recordset->valid());
$recordset->close();
// Create another book and chapter.
$book2 = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));
$bookgenerator->create_chapter(array('bookid' => $book2->id,
'content' => 'Chapter3', 'title' => 'Title3'));
// Query by context, first book.
$recordset = $searcharea->get_document_recordset(0, \context_module::instance($book->cmid));
$this->assertEquals(2, iterator_count($recordset));
$recordset->close();
// Second book.
$recordset = $searcharea->get_document_recordset(0, \context_module::instance($book2->cmid));
$this->assertEquals(1, iterator_count($recordset));
$recordset->close();
// Course.
$recordset = $searcharea->get_document_recordset(0, \context_course::instance($course1->id));
$this->assertEquals(3, iterator_count($recordset));
$recordset->close();
}
/**
* Document contents.
*
* @return void
*/
public function test_check_access(): void {
global $DB;
// Returns the instance as long as the area is supported.
$searcharea = \core_search\manager::get_search_area($this->bookchapterareaid);
$this->assertInstanceOf('\mod_book\search\chapter', $searcharea);
$user1 = self::getDataGenerator()->create_user();
$course1 = self::getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
$book = $this->getDataGenerator()->create_module('book', array('course' => $course1->id));
$bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
$chapter = array('bookid' => $book->id, 'content' => 'Chapter1', 'title' => 'Title1');
$chapter1 = $bookgenerator->create_chapter($chapter);
$chapter['content'] = 'Chapter2';
$chapter['title'] = 'Title2';
$chapter['hidden'] = 1;
$chapter2 = $bookgenerator->create_chapter($chapter);
$this->setAdminUser();
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($chapter1->id));
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($chapter2->id));
$this->setUser($user1);
$this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($chapter1->id));
$this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($chapter2->id));
$this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access($chapter2->id + 10));
}
}
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* booktool_exportimscp book exported event.
*
* @package booktool_exportimscp
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace booktool_exportimscp\event;
defined('MOODLE_INTERNAL') || die();
/**
* booktool_exportimscp book exported event class.
*
* @package booktool_exportimscp
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class book_exported extends \core\event\base {
/**
* Create instance of event.
*
* @since Moodle 2.7
*
* @param \stdClass $book
* @param \context_module $context
* @return book_exported
*/
public static function create_from_book(\stdClass $book, \context_module $context) {
$data = array(
'context' => $context,
'objectid' => $book->id
);
/** @var book_exported $event */
$event = self::create($data);
$event->add_record_snapshot('book', $book);
return $event;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has exported the book with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventbookexported', 'booktool_exportimscp');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/book/tool/exportimscp/index.php', array('id' => $this->contextinstanceid));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
$this->data['objecttable'] = 'book';
}
public static function get_objectid_mapping() {
return array('db' => 'book', 'restore' => 'book');
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy provider implementation for booktool_exportimscp.
*
* @package booktool_exportimscp
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace booktool_exportimscp\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider implementation for booktool_exportimscp.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book module capability definition
*
* @package booktool_exportimscp
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$capabilities = array(
'booktool/exportimscp:export' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE
),
);
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Export to IMSCP booktool log events definition
*
* @package booktool_exportimscp
* @copyright 2012 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$logs = array(
array('module'=>'book', 'action'=>'exportimscp', 'mtable'=>'book', 'field'=>'name')
);
+40
View File
@@ -0,0 +1,40 @@
body,
table,
td,
th,
li,
p {
font-family: Arial, Verdana, Helvetica, sans-serif;
font-size: 1em;
line-height: 150%;
letter-spacing: 0.02em;
}
th {
font-weight: bold;
}
a:link,
a:visited {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1.main,
h2.main,
h3.main,
h4.main,
h5.main,
h6.main {
font-weight: bold;
}
h1#header {
color: #666;
text-align: right;
padding-bottom: 0.2em;
border-bottom: solid 1px #666;
}
+50
View File
@@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book IMSCP export plugin
*
* @package booktool_exportimscp
* @copyright 2001-3001 Antonio Vicent {@link http://ludens.es}
* @copyright 2001-3001 Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../../../config.php');
require_once(__DIR__.'/locallib.php');
require_once($CFG->dirroot.'/mod/book/locallib.php');
require_once($CFG->libdir.'/filelib.php');
$id = required_param('id', PARAM_INT); // Course Module ID
$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
$PAGE->set_url('/mod/book/tool/exportimscp/index.php', array('id'=>$id));
require_login($course, false, $cm);
$context = context_module::instance($cm->id);
require_capability('mod/book:read', $context);
require_capability('booktool/exportimscp:export', $context);
\booktool_exportimscp\event\book_exported::create_from_book($book, $context)->trigger();
$file = booktool_exportimscp_build_package($book, $context);
send_stored_file($file, 10, 0, true, array('filename' => clean_filename($book->name).'.zip'));
@@ -0,0 +1,32 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* exportimscp booktool language strings
*
* @package booktool_exportimscp
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$string['eventbookexported'] = 'Book exported';
$string['exportimscp:export'] = 'Export book as IMS content package';
$string['generateimscp'] = 'Download IMS CP';
$string['nochapters'] = 'No book chapters found, so unable to export to IMS CP.';
$string['pluginname'] = 'Book IMS CP export';
$string['privacy:metadata'] = 'The Book IMS CP export plugin does not store any personal data.';
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* IMSCP export lib
*
* @package booktool_exportimscp
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Adds module specific settings to the settings block
*
* @param settings_navigation $settings The settings navigation object
* @param navigation_node $node The node to add module settings to
*/
function booktool_exportimscp_extend_settings_navigation(settings_navigation $settings, navigation_node $node) {
if (has_capability('booktool/exportimscp:export', $settings->get_page()->cm->context)) {
$url = new moodle_url('/mod/book/tool/exportimscp/index.php', array('id' => $settings->get_page()->cm->id));
$icon = new pix_icon('generate', '', 'booktool_exportimscp', array('class'=>'icon'));
$umscpnode = $node->add(get_string('generateimscp', 'booktool_exportimscp'), $url,
navigation_node::TYPE_SETTING, null, null, $icon);
$umscpnode->set_force_into_more_menu(true);
}
}
+262
View File
@@ -0,0 +1,262 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book imscp export lib
*
* @package booktool_exportimscp
* @copyright 2001-3001 Antonio Vicent {@link http://ludens.es}
* @copyright 2001-3001 Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__.'/lib.php');
require_once($CFG->dirroot.'/mod/book/locallib.php');
/**
* Export one book as IMSCP package
*
* @param stdClass $book book instance
* @param context_module $context
* @return bool|stored_file
*/
function booktool_exportimscp_build_package($book, $context) {
global $DB;
$fs = get_file_storage();
if ($packagefile = $fs->get_file($context->id, 'booktool_exportimscp', 'package', $book->revision, '/', 'imscp.zip')) {
return $packagefile;
}
// fix structure and test if chapters present
if (!book_preload_chapters($book)) {
throw new \moodle_exception('nochapters', 'booktool_exportimscp');
}
// prepare temp area with package contents
booktool_exportimscp_prepare_files($book, $context);
$packer = get_file_packer('application/zip');
$areafiles = $fs->get_area_files($context->id, 'booktool_exportimscp', 'temp', $book->revision, "sortorder, itemid, filepath, filename", false);
$files = array();
foreach ($areafiles as $file) {
$path = $file->get_filepath().$file->get_filename();
$path = ltrim($path, '/');
$files[$path] = $file;
}
unset($areafiles);
$packagefile = $packer->archive_to_storage($files, $context->id, 'booktool_exportimscp', 'package', $book->revision, '/', 'imscp.zip');
// drop temp area
$fs->delete_area_files($context->id, 'booktool_exportimscp', 'temp', $book->revision);
// delete older versions
$sql = "SELECT DISTINCT itemid
FROM {files}
WHERE contextid = :contextid AND component = 'booktool_exportimscp' AND itemid < :revision";
$params = array('contextid'=>$context->id, 'revision'=>$book->revision);
$revisions = $DB->get_records_sql($sql, $params);
foreach ($revisions as $rev => $unused) {
$fs->delete_area_files($context->id, 'booktool_exportimscp', 'temp', $rev);
$fs->delete_area_files($context->id, 'booktool_exportimscp', 'package', $rev);
}
return $packagefile;
}
/**
* Prepare temp area with the files used by book html contents
*
* @param stdClass $book book instance
* @param context_module $context
*/
function booktool_exportimscp_prepare_files($book, $context) {
global $CFG, $DB;
$fs = get_file_storage();
$temp_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp', 'itemid'=>$book->revision);
$chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
$chapterresources = array();
foreach ($chapters as $chapter) {
$chapterresources[$chapter->id] = array();
$files = $fs->get_area_files($context->id, 'mod_book', 'chapter', $chapter->id, "sortorder, itemid, filepath, filename", false);
foreach ($files as $file) {
$temp_file_record['filepath'] = '/'.$chapter->pagenum.$file->get_filepath();
$fs->create_file_from_storedfile($temp_file_record, $file);
$chapterresources[$chapter->id][] = $chapter->pagenum.$file->get_filepath().$file->get_filename();
}
if ($file = $fs->get_file($context->id, 'booktool_exportimscp', 'temp', $book->revision, "/$chapter->pagenum/", 'index.html')) {
// this should not exist
$file->delete();
}
$content = booktool_exportimscp_chapter_content($chapter, $context);
$index_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
'itemid'=>$book->revision, 'filepath'=>"/$chapter->pagenum/", 'filename'=>'index.html');
$fs->create_file_from_string($index_file_record, $content);
}
$css_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
'itemid'=>$book->revision, 'filepath'=>"/css/", 'filename'=>'styles.css');
$fs->create_file_from_pathname($css_file_record, __DIR__.'/imscp.css');
// Init imsmanifest and others
$imsmanifest = '';
$imsitems = '';
$imsresources = '';
// Moodle and Book version
$moodle_release = $CFG->release;
$moodle_version = $CFG->version;
$book_version = get_config('mod_book', 'version');
$bookname = format_string($book->name, true, array('context'=>$context));
// Load manifest header
$imsmanifest .= '<?xml version="1.0" encoding="UTF-8"?>
<!-- This package has been created with Moodle ' . $moodle_release . ' (' . $moodle_version . ') http://moodle.org/, Book module version ' . $book_version . ' - https://github.com/skodak/moodle-mod_book -->
<!-- One idea and implementation by Eloy Lafuente (stronk7) and Antonio Vicent (C) 2001-3001 -->
<manifest xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_v1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" identifier="MANIFEST-' . md5($CFG->wwwroot . '-' . $book->course . '-' . $book->id) . '" xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p2.xsd">
<organizations default="MOODLE-' . $book->course . '-' . $book->id . '">
<organization identifier="MOODLE-' . $book->course . '-' . $book->id . '" structure="hierarchical">
<title>' . htmlspecialchars($bookname, ENT_COMPAT) . '</title>';
// To store the prev level (book only have 0 and 1)
$prevlevel = null;
$currlevel = 0;
foreach ($chapters as $chapter) {
// Calculate current level ((book only have 0 and 1)
$currlevel = empty($chapter->subchapter) ? 0 : 1;
// Based upon prevlevel and current one, decide what to close
if ($prevlevel !== null) {
// Calculate the number of spaces (for visual xml-text formating)
$prevspaces = substr(' ', 0, $currlevel * 2);
// Same level, simply close the item
if ($prevlevel == $currlevel) {
$imsitems .= $prevspaces . ' </item>' . "\n";
}
// Bigger currlevel, nothing to close
// Smaller currlevel, close both the current item and the parent one
if ($prevlevel > $currlevel) {
$imsitems .= ' </item>' . "\n";
$imsitems .= ' </item>' . "\n";
}
}
// Update prevlevel
$prevlevel = $currlevel;
// Calculate the number of spaces (for visual xml-text formatting)
$currspaces = substr(' ', 0, $currlevel * 2);
$chaptertitle = format_string($chapter->title, true, array('context'=>$context));
// Add the imsitems
$imsitems .= $currspaces .' <item identifier="ITEM-' . $book->course . '-' . $book->id . '-' . $chapter->pagenum .'" isvisible="true" identifierref="RES-' .
$book->course . '-' . $book->id . '-' . $chapter->pagenum . "\">\n" .
$currspaces . ' <title>' . htmlspecialchars($chaptertitle, ENT_COMPAT) . '</title>' . "\n";
// Add the imsresources
// First, check if we have localfiles
$localfiles = array();
foreach ($chapterresources[$chapter->id] as $localfile) {
$localfiles[] = "\n" . ' <file href="' . $localfile . '" />';
}
// Now add the dependency to css
$cssdependency = "\n" . ' <dependency identifierref="RES-' . $book->course . '-' . $book->id . '-css" />';
// Now build the resources section
$imsresources .= ' <resource identifier="RES-' . $book->course . '-' . $book->id . '-' . $chapter->pagenum . '" type="webcontent" xml:base="' .
$chapter->pagenum . '/" href="index.html">' . "\n" .
' <file href="' . $chapter->pagenum . '/index.html" />' . implode($localfiles) . $cssdependency . "\n".
' </resource>' . "\n";
}
// Close items (the latest chapter)
// Level 1, close 1
if ($currlevel == 0) {
$imsitems .= ' </item>' . "\n";
}
// Level 2, close 2
if ($currlevel == 1) {
$imsitems .= ' </item>' . "\n";
$imsitems .= ' </item>' . "\n";
}
// Define the css common resource
$cssresource = ' <resource identifier="RES-' . $book->course . '-' . $book->id . '-css" type="webcontent" xml:base="css/" href="styles.css">
<file href="css/styles.css" />
</resource>' . "\n";
// Add imsitems to manifest
$imsmanifest .= "\n" . $imsitems;
// Close the organization
$imsmanifest .= " </organization>
</organizations>";
// Add resources to manifest
$imsmanifest .= "\n <resources>\n" . $imsresources . $cssresource . " </resources>";
// Close manifest
$imsmanifest .= "\n</manifest>\n";
$manifest_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
'itemid'=>$book->revision, 'filepath'=>"/", 'filename'=>'imsmanifest.xml');
$fs->create_file_from_string($manifest_file_record, $imsmanifest);
}
/**
* Returns the html contents of one book's chapter to be exported as IMSCP
*
* @param stdClass $chapter the chapter to be exported
* @param context_module $context context the chapter belongs to
* @return string the contents of the chapter
*/
function booktool_exportimscp_chapter_content($chapter, $context) {
$options = new stdClass();
$options->noclean = true;
$options->context = $context;
// We need to rewrite the pluginfile URLs so the media filters can work.
$chaptercontent = file_rewrite_pluginfile_urls($chapter->content, 'pluginfile.php', $context->id, 'mod_book', 'chapter',
$chapter->id);
$chaptercontent = format_text($chaptercontent, $chapter->contentformat, $options);
// Now remove again the full pluginfile URLs.
$options = array('reverse' => true);
$chaptercontent = file_rewrite_pluginfile_urls($chaptercontent, 'pluginfile.php', $context->id, 'mod_book', 'chapter',
$chapter->id, $options);
$chaptercontent = str_replace('@@PLUGINFILE@@/', '', $chaptercontent);
$chaptertitle = format_string($chapter->title, true, array('context'=>$context));
$content = '';
$content .= '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">' . "\n";
$content .= '<html>' . "\n";
$content .= '<head>' . "\n";
$content .= '<meta http-equiv="content-type" content="text/html; charset=utf-8" />' . "\n";
$content .= '<link rel="stylesheet" type="text/css" href="../css/styles.css" />' . "\n";
$content .= '<title>' . $chaptertitle . '</title>' . "\n";
$content .= '</head>' . "\n";
$content .= '<body>' . "\n";
$content .= '<h1 id="header">' . $chaptertitle . '</h1>' ."\n";
$content .= $chaptercontent . "\n";
$content .= '</body>' . "\n";
$content .= '</html>' . "\n";
return $content;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

@@ -0,0 +1,65 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Events tests.
*
* @package booktool_exportimscp
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace booktool_exportimscp\event;
/**
* Events tests class.
*
* @package booktool_exportimscp
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
public function test_book_exported(): void {
// There is no proper API to call to test the event, so what we are
// doing here is simply making sure that the events returns the right information.
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$context = \context_module::instance($book->cmid);
$event = \booktool_exportimscp\event\book_exported::create_from_book($book, $context);
// Triggering and capturing the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\booktool_exportimscp\event\book_exported', $event);
$this->assertEquals(\context_module::instance($book->cmid), $event->get_context());
$this->assertEquals($book->id, $event->objectid);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book IMSCP export plugin version info
*
* @package booktool_exportimscp
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->component = 'booktool_exportimscp'; // Full name of the plugin (used for diagnostics)
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy provider implementation for booktool_importhtml.
*
* @package booktool_importhtml
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace booktool_importhtml\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider implementation for booktool_importhtml.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book import capability definition
*
* @package booktool_importhtml
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$capabilities = array(
'booktool/importhtml:import' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
),
);
+88
View File
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book import form
*
* @package booktool_importhtml
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir.'/formslib.php');
class booktool_importhtml_form extends moodleform {
function definition() {
$mform = $this->_form;
$data = $this->_customdata;
$mform->addElement('header', 'general', get_string('import', 'booktool_importhtml'));
$options = array(
// '0'=>get_string('typeonefile', 'booktool_importhtml'),
'1'=>get_string('typezipdirs', 'booktool_importhtml'),
'2'=>get_string('typezipfiles', 'booktool_importhtml'),
);
$mform->addElement('select', 'type', get_string('type', 'booktool_importhtml'), $options);
$mform->setDefault('type', 2);
$mform->addElement('filepicker', 'importfile', get_string('ziparchive', 'booktool_importhtml'));
$mform->addHelpButton('importfile', 'ziparchive', 'booktool_importhtml');
$mform->addRule('importfile', null, 'required');
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'chapterid');
$mform->setType('chapterid', PARAM_INT);
$this->add_action_buttons(true, get_string('doimport', 'booktool_importhtml'));
$this->set_data($data);
}
function validation($data, $files) {
global $USER;
if ($errors = parent::validation($data, $files)) {
return $errors;
}
$usercontext = context_user::instance($USER->id);
$fs = get_file_storage();
if (!$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $data['importfile'], 'id', false)) {
$errors['importfile'] = get_string('required');
return $errors;
} else {
$file = reset($files);
if ($file->get_mimetype() != 'application/zip') {
$errors['importfile'] = get_string('invalidfiletype', 'error', $file->get_filename());
// better delete current file, it is not usable anyway
$fs->delete_area_files($usercontext->id, 'user', 'draft', $data['importfile']);
} else {
if (!$chpterfiles = toolbook_importhtml_get_chapter_files($file, $data['type'])) {
$errors['importfile'] = get_string('errornochapters', 'booktool_importhtml');
}
}
}
return $errors;
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book import
*
* @package booktool_importhtml
* @copyright 2004-2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../../../config.php');
require_once(__DIR__.'/locallib.php');
require_once(__DIR__.'/import_form.php');
$id = required_param('id', PARAM_INT); // Course Module ID
$chapterid = optional_param('chapterid', 0, PARAM_INT); // Chapter ID
$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
$context = context_module::instance($cm->id);
require_capability('booktool/importhtml:import', $context);
$PAGE->set_url('/mod/book/tool/importhtml/index.php', array('id' => $id));
if ($chapterid) {
if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id))) {
$chapterid = 0;
}
} else {
$chapter = false;
}
$PAGE->set_title($book->name);
$PAGE->set_heading($course->fullname);
$PAGE->activityheader->set_attrs([
'hidecompletion' => true,
'description' => ''
]);
// Prepare the page header.
$strbook = get_string('modulename', 'mod_book');
$strbooks = get_string('modulenameplural', 'mod_book');
$mform = new booktool_importhtml_form(null, array('id'=>$id, 'chapterid'=>$chapterid));
// If data submitted, then process and store.
if ($mform->is_cancelled()) {
if (empty($chapter->id)) {
redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id");
} else {
redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
}
} else if ($data = $mform->get_data()) {
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('importingchapters', 'booktool_importhtml'), 3);
// this is a bloody hack - children do not try this at home!
$fs = get_file_storage();
$draftid = file_get_submitted_draft_itemid('importfile');
if (!$files = $fs->get_area_files(context_user::instance($USER->id)->id, 'user', 'draft', $draftid, 'id DESC', false)) {
redirect($PAGE->url);
}
$file = reset($files);
toolbook_importhtml_import_chapters($file, $data->type, $book, $context);
echo $OUTPUT->continue_button(new moodle_url('/mod/book/view.php', array('id'=>$id)));
echo $OUTPUT->footer();
die;
}
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Book import language strings
*
* @package booktool_importhtml
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$string['doimport'] = 'Import';
$string['errornochapters'] = 'Cannot find chapters in selected file';
$string['import'] = 'Import chapter';
$string['importhtml:import'] = 'Import chapters';
$string['importing'] = 'Importing';
$string['importingchapters'] = 'Importing chapters into book';
$string['pluginname'] = 'Book chapter import';
$string['privacy:metadata'] = 'The Book chapter import plugin does not store any personal data.';
$string['relinking'] = 'Relinking';
$string['type'] = 'Type';
$string['typeonefile'] = 'One HTML file with headings as chapters';
$string['typezipfiles'] = 'Each HTML file represents one chapter';
$string['typezipdirs'] = 'Each folder represents one chapter';
$string['ziparchive'] = 'Zip file';
$string['ziparchive_help'] = 'Select a zip file containing HTML files and optional multimedia files and folders. To upload subchapters, add "_sub" to the end of HTML file or folder names.';
+38
View File
@@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* HTML import lib
*
* @package booktool_importhtml
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* Adds module specific settings to the settings block
*
* @param settings_navigation $settings The settings navigation object
* @param navigation_node $node The node to add module settings to
*/
function booktool_importhtml_extend_settings_navigation(settings_navigation $settings, navigation_node $node) {
if (has_capability('booktool/importhtml:import', $settings->get_page()->cm->context)) {
$url = new moodle_url('/mod/book/tool/importhtml/index.php', array('id' => $settings->get_page()->cm->id));
$node->add(get_string('import', 'booktool_importhtml'), $url, navigation_node::TYPE_SETTING, null, 'importchapter', null);
}
}
+346
View File
@@ -0,0 +1,346 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* HTML import lib
*
* @package booktool_importhtml
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__.'/lib.php');
require_once($CFG->dirroot.'/mod/book/locallib.php');
/**
* Import HTML pages packaged into one zip archive
*
* @param stored_file $package
* @param string $type type of the package ('typezipdirs' or 'typezipfiles')
* @param stdClass $book
* @param context_module $context
* @param bool $verbose
*/
function toolbook_importhtml_import_chapters($package, $type, $book, $context, $verbose = true) {
global $DB, $OUTPUT;
$fs = get_file_storage();
$chapterfiles = toolbook_importhtml_get_chapter_files($package, $type);
$packer = get_file_packer('application/zip');
$fs->delete_area_files($context->id, 'mod_book', 'importhtmltemp', 0);
$package->extract_to_storage($packer, $context->id, 'mod_book', 'importhtmltemp', 0, '/');
// $datafiles = $fs->get_area_files($context->id, 'mod_book', 'importhtmltemp', 0, 'id', false);
// echo "<pre>";p(var_export($datafiles, true));
$chapters = array();
if ($verbose) {
echo $OUTPUT->notification(get_string('importing', 'booktool_importhtml'), 'notifysuccess');
}
if ($type == 0) {
$chapterfile = reset($chapterfiles);
if ($file = $fs->get_file_by_hash(sha1("$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname"))) {
$htmlcontent = toolbook_importhtml_fix_encoding($file->get_content());
$htmlchapters = toolbook_importhtml_parse_headings(toolbook_importhtml_parse_body($htmlcontent));
// TODO: process h1 as main chapter and h2 as subchapters
}
} else {
foreach ($chapterfiles as $chapterfile) {
if ($file = $fs->get_file_by_hash(sha1("/$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname"))) {
$chapter = new stdClass();
$htmlcontent = toolbook_importhtml_fix_encoding($file->get_content());
$chapter->bookid = $book->id;
$chapter->pagenum = $DB->get_field_sql('SELECT MAX(pagenum) FROM {book_chapters} WHERE bookid = ?', array($book->id)) + 1;
$chapter->importsrc = '/'.$chapterfile->pathname;
$chapter->content = toolbook_importhtml_parse_styles($htmlcontent);
$chapter->content .= toolbook_importhtml_parse_body($htmlcontent);
$chapter->title = toolbook_importhtml_parse_title($htmlcontent, $chapterfile->pathname);
$chapter->contentformat = FORMAT_HTML;
$chapter->hidden = 0;
$chapter->timecreated = time();
$chapter->timemodified = time();
if (preg_match('/_sub(\/|\.htm)/i', $chapter->importsrc)) { // If filename or directory ends with *_sub treat as subchapters
$chapter->subchapter = 1;
} else {
$chapter->subchapter = 0;
}
$chapter->id = $DB->insert_record('book_chapters', $chapter);
$chapter = $DB->get_record('book_chapters', array('id' => $chapter->id));
$chapters[$chapter->id] = $chapter;
\mod_book\event\chapter_created::create_from_chapter($book, $context, $chapter)->trigger();
}
}
}
if ($verbose) {
echo $OUTPUT->notification(get_string('relinking', 'booktool_importhtml'), 'notifysuccess');
}
$allchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
foreach ($chapters as $chapter) {
// find references to all files and copy them + relink them
$matches = null;
if (preg_match_all('/(src|codebase|name|href)\s*=\s*"([^"]+)"/i', $chapter->content, $matches)) {
$file_record = array('contextid'=>$context->id, 'component'=>'mod_book', 'filearea'=>'chapter', 'itemid'=>$chapter->id);
foreach ($matches[0] as $i => $match) {
$filepath = dirname($chapter->importsrc).'/'.$matches[2][$i];
$filepath = toolbook_importhtml_fix_path($filepath);
if (strtolower($matches[1][$i]) === 'href') {
// skip linked html files, we will try chapter relinking later
foreach ($allchapters as $target) {
if ($target->importsrc === $filepath) {
continue 2;
}
}
}
if ($file = $fs->get_file_by_hash(sha1("/$context->id/mod_book/importhtmltemp/0$filepath"))) {
if (!$oldfile = $fs->get_file_by_hash(sha1("/$context->id/mod_book/chapter/$chapter->id$filepath"))) {
$fs->create_file_from_storedfile($file_record, $file);
}
$chapter->content = str_replace($match, $matches[1][$i].'="@@PLUGINFILE@@'.$filepath.'"', $chapter->content);
}
}
$DB->set_field('book_chapters', 'content', $chapter->content, array('id'=>$chapter->id));
}
}
unset($chapters);
$allchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
foreach ($allchapters as $chapter) {
$newcontent = $chapter->content;
$matches = null;
if (preg_match_all('/(href)\s*=\s*"([^"]+)"/i', $chapter->content, $matches)) {
foreach ($matches[0] as $i => $match) {
if (strpos($matches[2][$i], ':') !== false or strpos($matches[2][$i], '@') !== false) {
// it is either absolute or pluginfile link
continue;
}
$chapterpath = dirname($chapter->importsrc).'/'.$matches[2][$i];
$chapterpath = toolbook_importhtml_fix_path($chapterpath);
foreach ($allchapters as $target) {
if ($target->importsrc === $chapterpath) {
$newcontent = str_replace($match, 'href="'.new moodle_url('/mod/book/view.php',
array('id'=>$context->instanceid, 'chapterid'=>$target->id)).'"', $newcontent);
}
}
}
}
if ($newcontent !== $chapter->content) {
$DB->set_field('book_chapters', 'content', $newcontent, array('id'=>$chapter->id));
}
}
$fs->delete_area_files($context->id, 'mod_book', 'importhtmltemp', 0);
// update the revision flag - this takes a long time, better to refetch the current value
$book = $DB->get_record('book', array('id'=>$book->id));
$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
}
/**
* Parse the headings of the imported package of type 'typeonefile'
* (currently unsupported)
*
* @param string $html html content to parse
* @todo implement this once the type 'typeonefile' is enabled
*/
function toolbook_importhtml_parse_headings($html) {
}
/**
* Parse the links to external css sheets of the imported html content
*
* @param string $html html content to parse
* @return string all the links to external css sheets
*/
function toolbook_importhtml_parse_styles($html) {
$styles = '';
if (preg_match('/<head[^>]*>(.+)<\/head>/is', $html, $matches)) {
$head = $matches[1];
if (preg_match_all('/<link[^>]+rel="stylesheet"[^>]*>/i', $head, $matches)) { // Extract links to css.
for ($i=0; $i<count($matches[0]); $i++) {
$styles .= $matches[0][$i]."\n";
}
}
}
return $styles;
}
/**
* Normalize paths to be absolute
*
* @param string $path original path with MS/relative separators
* @return string the normalized and cleaned absolute path
*/
function toolbook_importhtml_fix_path($path) {
$path = str_replace('\\', '/', $path); // anti MS hack
$path = '/'.ltrim($path, './'); // dirname() produces . for top level files + our paths start with /
$cnt = substr_count($path, '..');
for ($i=0; $i<$cnt; $i++) {
$path = preg_replace('|[^/]+/\.\./|', '', $path, 1);
}
$path = clean_param($path, PARAM_PATH);
return $path;
}
/**
* Convert some html content to utf8, getting original encoding from html headers
*
* @param string $html html content to convert
* @return string html content converted to utf8
*/
function toolbook_importhtml_fix_encoding($html) {
if (preg_match('/<head[^>]*>(.+)<\/head>/is', $html, $matches)) {
$head = $matches[1];
if (preg_match('/charset=([^"]+)/is', $head, $matches)) {
$enc = $matches[1];
return core_text::convert($html, $enc, 'utf-8');
}
}
return iconv('UTF-8', 'UTF-8//IGNORE', $html);
}
/**
* Extract the body from any html contents
*
* @param string $html the html to parse
* @return string the contents of the body
*/
function toolbook_importhtml_parse_body($html) {
$matches = null;
if (preg_match('/<body[^>]*>(.+)<\/body>/is', $html, $matches)) {
return $matches[1];
} else {
return '';
}
}
/**
* Extract the title of any html content, getting it from the title tag
*
* @param string $html the html to parse
* @param string $default default title to apply if no title is found
* @return string the resulting title
*/
function toolbook_importhtml_parse_title($html, $default) {
$matches = null;
if (preg_match('/<title>([^<]+)<\/title>/i', $html, $matches)) {
return $matches[1];
} else {
return $default;
}
}
/**
* Returns all the html files (chapters) from a file package
*
* @param stored_file $package file to be processed
* @param string $type type of the package ('typezipdirs' or 'typezipfiles')
*
* @return array the html files found in the package
*/
function toolbook_importhtml_get_chapter_files($package, $type) {
$packer = get_file_packer('application/zip');
$files = $package->list_files($packer);
$tophtmlfiles = array();
$subhtmlfiles = array();
$topdirs = array();
foreach ($files as $file) {
if (empty($file->pathname)) {
continue;
}
if (substr($file->pathname, -1) === '/') {
if (substr_count($file->pathname, '/') !== 1) {
// skip subdirs
continue;
}
if (!isset($topdirs[$file->pathname])) {
$topdirs[$file->pathname] = array();
}
} else {
$mime = mimeinfo('icon', $file->pathname);
if ($mime !== 'markup') {
continue;
}
$level = substr_count($file->pathname, '/');
if ($level === 0) {
$tophtmlfiles[$file->pathname] = $file;
} else if ($level === 1) {
$subhtmlfiles[$file->pathname] = $file;
$dir = preg_replace('|/.*$|', '', $file->pathname);
$topdirs[$dir][$file->pathname] = $file;
} else {
// lower levels are not interesting
continue;
}
}
}
core_collator::ksort($tophtmlfiles, core_collator::SORT_NATURAL);
core_collator::ksort($subhtmlfiles, core_collator::SORT_NATURAL);
core_collator::ksort($topdirs, core_collator::SORT_NATURAL);
$chapterfiles = array();
if ($type == 2) {
$chapterfiles = $tophtmlfiles;
} else if ($type == 1) {
foreach ($topdirs as $dir => $htmlfiles) {
if (empty($htmlfiles)) {
continue;
}
core_collator::ksort($htmlfiles, core_collator::SORT_NATURAL);
if (isset($htmlfiles[$dir.'/index.html'])) {
$htmlfile = $htmlfiles[$dir.'/index.html'];
} else if (isset($htmlfiles[$dir.'/index.htm'])) {
$htmlfile = $htmlfiles[$dir.'/index.htm'];
} else if (isset($htmlfiles[$dir.'/Default.htm'])) {
$htmlfile = $htmlfiles[$dir.'/Default.htm'];
} else {
$htmlfile = reset($htmlfiles);
}
$chapterfiles[$htmlfile->pathname] = $htmlfile;
}
} else if ($type == 0) {
if ($tophtmlfiles) {
if (isset($tophtmlfiles['index.html'])) {
$htmlfile = $tophtmlfiles['index.html'];
} else if (isset($tophtmlfiles['index.htm'])) {
$htmlfile = $tophtmlfiles['index.htm'];
} else if (isset($tophtmlfiles['Default.htm'])) {
$htmlfile = $tophtmlfiles['Default.htm'];
} else {
$htmlfile = reset($tophtmlfiles);
}
} else {
$htmlfile = reset($subhtmlfiles);
}
$chapterfiles[$htmlfile->pathname] = $htmlfile;
}
return $chapterfiles;
}
Binary file not shown.
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* booktool_importhtml tests.
*
* @package booktool_importhtml
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace booktool_importhtml;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/book/tool/importhtml/locallib.php');
/**
* booktool_importhtml tests class.
*
* @package booktool_importhtml
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class locallib_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
public function test_import_chapters_events(): void {
$course = $this->getDataGenerator()->create_course();
$book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
$context = \context_module::instance($book->cmid);
$record = new \stdClass();
$record->contextid = $context->id;
$record->component = 'phpunit';
$record->filearea = 'test';
$record->itemid = 0;
$record->filepath = '/';
$record->filename = 'chapters.zip';
$fs = get_file_storage();
$file = $fs->create_file_from_pathname($record, __DIR__ . '/fixtures/chapters.zip');
// Importing the chapters.
$sink = $this->redirectEvents();
toolbook_importhtml_import_chapters($file, 2, $book, $context, false);
$events = $sink->get_events();
// Checking the results.
$this->assertCount(5, $events);
foreach ($events as $event) {
$this->assertInstanceOf('\mod_book\event\chapter_created', $event);
$this->assertEquals($context, $event->get_context());
$chapter = $event->get_record_snapshot('book_chapters', $event->objectid);
$this->assertNotEmpty($chapter);
$this->assertEventContextNotUsed($event);
}
}
}

Some files were not shown because too many files have changed in this diff Show More