From 01783146820a7efc33fc10b0de1ca920aa441c05 Mon Sep 17 00:00:00 2001 From: Andrew Hancox <andrewdchancox@googlemail.com> Date: Fri, 24 Mar 2017 15:34:45 +0000 Subject: [PATCH] MDL-57457 mod_book: Implement tagging --- .../backup/moodle2/backup_book_stepslib.php | 22 ++++ .../backup/moodle2/restore_book_stepslib.php | 20 ++- mod/book/db/tag.php | 35 ++++++ mod/book/delete.php | 2 + mod/book/edit.php | 13 +- mod/book/edit_form.php | 7 ++ mod/book/lang/en/book.php | 5 + mod/book/locallib.php | 114 ++++++++++++++++++ mod/book/tests/behat/edit_tags.feature | 73 +++++++++++ mod/book/tests/generator/lib.php | 8 ++ mod/book/tests/generator_test.php | 5 +- mod/book/tests/lib_test.php | 90 ++++++++++++++ mod/book/version.php | 2 +- mod/book/view.php | 4 + 14 files changed, 395 insertions(+), 5 deletions(-) create mode 100644 mod/book/db/tag.php create mode 100644 mod/book/tests/behat/edit_tags.feature diff --git a/mod/book/backup/moodle2/backup_book_stepslib.php b/mod/book/backup/moodle2/backup_book_stepslib.php index 50fbd51a4cb..a87cad677fa 100644 --- a/mod/book/backup/moodle2/backup_book_stepslib.php +++ b/mod/book/backup/moodle2/backup_book_stepslib.php @@ -31,6 +31,9 @@ class backup_book_activity_structure_step extends backup_activity_structure_step protected function define_structure() { + // To know if we are including userinfo. + $userinfo = $this->get_setting_value('userinfo'); + // Define each element separated. $book = new backup_nested_element('book', array('id'), array( 'name', 'intro', 'introformat', 'numbering', 'navstyle', @@ -40,6 +43,9 @@ class backup_book_activity_structure_step extends backup_activity_structure_step 'pagenum', 'subchapter', 'title', 'content', 'contentformat', 'hidden', 'timemcreated', 'timemodified', 'importsrc')); + $tags = new backup_nested_element('tags'); + $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname')); + $book->add_child($chapters); $chapters->add_child($chapter); @@ -51,6 +57,22 @@ class backup_book_activity_structure_step extends backup_activity_structure_step $book->annotate_files('mod_book', 'intro', null); // This file area hasn't itemid $chapter->annotate_files('mod_book', 'chapter', 'id'); + $chapter->add_child($tags); + $tags->add_child($tag); + + // All these source definitions only happen if we are including user info. + if ($userinfo) { + $tag->set_source_sql('SELECT t.id, t.name, t.rawname + FROM {tag} t + JOIN {tag_instance} ti ON ti.tagid = t.id + WHERE ti.itemtype = ? + AND ti.component = ? + AND ti.itemid = ?', array( + backup_helper::is_sqlparam('book_chapters'), + backup_helper::is_sqlparam('mod_book'), + backup::VAR_PARENTID)); + } + // Return the root element (book), wrapped into standard activity structure return $this->prepare_activity_structure($book); } diff --git a/mod/book/backup/moodle2/restore_book_stepslib.php b/mod/book/backup/moodle2/restore_book_stepslib.php index 93d826efa75..59757c19dd3 100644 --- a/mod/book/backup/moodle2/restore_book_stepslib.php +++ b/mod/book/backup/moodle2/restore_book_stepslib.php @@ -30,12 +30,16 @@ defined('MOODLE_INTERNAL') || die; class restore_book_activity_structure_step extends restore_activity_structure_step { protected function define_structure() { - $paths = array(); + $userinfo = $this->get_setting_value('userinfo'); $paths[] = new restore_path_element('book', '/activity/book'); $paths[] = new restore_path_element('book_chapter', '/activity/book/chapters/chapter'); + if ($userinfo) { + $paths[] = new restore_path_element('book_tag', '/activity/book/chapters/chapter/tags/tag'); + } + // Return the paths wrapped into standard activity structure return $this->prepare_activity_structure($paths); } @@ -72,6 +76,20 @@ class restore_book_activity_structure_step extends restore_activity_structure_st $this->set_mapping('book_chapter', $oldid, $newitemid, true); } + protected function process_book_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; + $itemid = $this->get_new_parentid('book_chapter'); + + $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; diff --git a/mod/book/db/tag.php b/mod/book/db/tag.php new file mode 100644 index 00000000000..55987e12b09 --- /dev/null +++ b/mod/book/db/tag.php @@ -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', + ), +); diff --git a/mod/book/delete.php b/mod/book/delete.php index d6ee5ef584a..a88e79da82d 100644 --- a/mod/book/delete.php +++ b/mod/book/delete.php @@ -65,6 +65,7 @@ if ($confirm) { 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(); @@ -76,6 +77,7 @@ if ($confirm) { } // 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]); diff --git a/mod/book/edit.php b/mod/book/edit.php index f3133532285..c3b4662f58d 100644 --- a/mod/book/edit.php +++ b/mod/book/edit.php @@ -75,9 +75,9 @@ if ($mform->is_cancelled()) { $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; @@ -102,6 +102,10 @@ if ($mform->is_cancelled()) { $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id)); $chapter = $DB->get_record('book_chapters', array('id' => $data->id)); + if (core_tag_tag::is_enabled('mod_book', 'book_chapters') && isset($data->tags)) { + 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(); } @@ -120,6 +124,11 @@ if ($chapters = book_preload_chapters($book)) { echo $OUTPUT->header(); echo $OUTPUT->heading($book->name); -$mform->display(); +if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) { + $data = new StdClass(); + $data->tags = core_tag_tag::get_item_tags_array('mod_book', 'book_chapters', $chapter->id); + $mform->set_data($data); + $mform->display(); +} echo $OUTPUT->footer(); diff --git a/mod/book/edit_form.php b/mod/book/edit_form.php index 118e5a70a1e..3aaf41b727d 100644 --- a/mod/book/edit_form.php +++ b/mod/book/edit_form.php @@ -58,6 +58,13 @@ class book_chapter_edit_form extends moodleform { $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); diff --git a/mod/book/lang/en/book.php b/mod/book/lang/en/book.php index e67db57cf0b..8f7ffb526e5 100644 --- a/mod/book/lang/en/book.php +++ b/mod/book/lang/en/book.php @@ -100,3 +100,8 @@ $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'; +$string['tagtitle'] = 'See the "{$a}" tag'; diff --git a/mod/book/locallib.php b/mod/book/locallib.php index cbae03465c5..52cb3d4aac1 100644 --- a/mod/book/locallib.php +++ b/mod/book/locallib.php @@ -417,6 +417,120 @@ function book_get_toc($chapters, $chapter, $book, $cm, $edit) { 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 diff --git a/mod/book/tests/behat/edit_tags.feature b/mod/book/tests/behat/edit_tags.feature new file mode 100644 index 00000000000..66a455e3d9e --- /dev/null +++ b/mod/book/tests/behat/edit_tags.feature @@ -0,0 +1,73 @@ +@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 "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I add a "Book" to section "1" and I fill the form with: + | Name | Test book | + | Description | A book about dreams! | + And I log out + + Scenario: Book chapter edition of custom tags works as expected + Given I log in as "teacher1" + And I follow "Course 1" + And I follow "Test book" + 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 press "Turn editing 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 I log in as "admin" + And I navigate to "Appearance > Manage tags" in site administration + And I follow "Default collection" + And I follow "Add standard tags" + And I set the field "Enter comma-separated list of new tags" to "OT1, OT2, OT3" + And I press "Continue" + And I log out + And I log in as "teacher1" + And I follow "Course 1" + And I follow "Test book" + And I click on ".form-autocomplete-downarrow" "css_element" + 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 press "Turn editing 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" diff --git a/mod/book/tests/generator/lib.php b/mod/book/tests/generator/lib.php index 80694b4b8ff..8471cf7ba30 100644 --- a/mod/book/tests/generator/lib.php +++ b/mod/book/tests/generator/lib.php @@ -117,6 +117,14 @@ class mod_book_generator extends testing_module_generator { 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; } diff --git a/mod/book/tests/generator_test.php b/mod/book/tests/generator_test.php index 49330295a64..4796cbff8ff 100644 --- a/mod/book/tests/generator_test.php +++ b/mod/book/tests/generator_test.php @@ -62,10 +62,13 @@ class mod_book_generator_testcase extends advanced_testcase { $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')); + $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))); diff --git a/mod/book/tests/lib_test.php b/mod/book/tests/lib_test.php index 39015a305b8..e7a1f132e5c 100644 --- a/mod/book/tests/lib_test.php +++ b/mod/book/tests/lib_test.php @@ -236,4 +236,94 @@ class mod_book_lib_testcase extends advanced_testcase { return calendar_event::create($event); } + + public function test_mod_book_get_tagged_chapters() { + 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->assertRegExp('/'.$chapter11->title.'</', $res->content); + $this->assertRegExp('/'.$chapter12->title.'</', $res->content); + $this->assertRegExp('/'.$chapter13->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter14->title.'</', $res->content); + $this->assertRegExp('/'.$chapter15->title.'</', $res->content); + $this->assertRegExp('/'.$chapter16->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter21->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter22->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter23->title.'</', $res->content); + $this->assertNotRegExp('/'.$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->assertNotRegExp('/'.$chapter11->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter12->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter13->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter14->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter15->title.'</', $res->content); + $this->assertNotRegExp('/'.$chapter16->title.'</', $res->content); + $this->assertRegExp('/'.$chapter21->title.'</', $res->content); + $this->assertRegExp('/'.$chapter22->title.'</', $res->content); + $this->assertRegExp('/'.$chapter23->title.'</', $res->content); + $this->assertRegExp('/'.$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->assertRegExp('/'.$chapter22->title.'/', $res->content); + $this->assertRegExp('/'.$chapter23->title.'/', $res->content); + $this->assertNotRegExp('/'.$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->assertRegExp('/'.$chapter11->title.'/', $res->content); + $this->assertRegExp('/'.$chapter12->title.'/', $res->content); + $this->assertRegExp('/'.$chapter13->title.'/', $res->content); + $this->assertNotRegExp('/'.$chapter14->title.'/', $res->content); + $this->assertRegExp('/'.$chapter15->title.'/', $res->content); + $this->assertNotRegExp('/'.$chapter21->title.'/', $res->content); + $this->assertNotRegExp('/'.$chapter22->title.'/', $res->content); + $this->assertNotRegExp('/'.$chapter23->title.'/', $res->content); + $this->assertEmpty($res->nextpageurl); + + // User cannot see hidden chapters. + $this->assertNotRegExp('/'.$chapter16->title.'/', $res->content); + } } diff --git a/mod/book/version.php b/mod/book/version.php index 61a28803bf9..70235c3bdb1 100644 --- a/mod/book/version.php +++ b/mod/book/version.php @@ -25,6 +25,6 @@ defined('MOODLE_INTERNAL') || die; $plugin->component = 'mod_book'; // Full name of the plugin (used for diagnostics) -$plugin->version = 2016120500; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2016120501; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2016112900; // Requires this Moodle version $plugin->cron = 0; // Period for cron to check this module (secs) diff --git a/mod/book/view.php b/mod/book/view.php index 5edc062cd9c..268cd704cad 100644 --- a/mod/book/view.php +++ b/mod/book/view.php @@ -237,6 +237,10 @@ echo format_text($chaptertext, $chapter->contentformat, array('noclean'=>true, ' echo $OUTPUT->box_end(); +if (core_tag_tag::is_enabled('mod_book', 'book_chapters')) { + echo $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_book', 'book_chapters', $chapter->id), null, 'book-tags'); +} + if ($book->navstyle) { // Lower navigation. echo '<div class="navbottom clearfix ' . $navclasses[$book->navstyle] . '">' . $chnavigation . '</div>';