diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 436f19f36dd25..b088b1a5a13c4 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -269,6 +269,9 @@ protected function define_structure() { 'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'availability', 'showdescription')); + $tags = new backup_nested_element('tags'); + $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname')); + // attach format plugin structure to $module element, only one allowed $this->add_plugin_structure('format', $module, false); @@ -279,6 +282,9 @@ protected function define_structure() { // attach local plugin structure to $module, multiple allowed $this->add_plugin_structure('local', $module, true); + $module->add_child($tags); + $tags->add_child($tag); + // Set the sources $concat = $DB->sql_concat("'mod_'", 'm.name'); $module->set_source_sql(" @@ -289,6 +295,13 @@ protected function define_structure() { JOIN {course_sections} s ON s.id = cm.section WHERE cm.id = ?", array(backup::VAR_MODID)); + $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 = 'course_modules' + AND ti.component = 'core' + AND ti.itemid = ?", array(backup::VAR_MODID)); + // Define annotations $module->annotate_ids('grouping', 'groupingid'); diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index caf5f071b10f3..ec316b6412e4e 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -3584,6 +3584,8 @@ protected function define_structure() { $paths[] = new restore_path_element('availability_field', '/module/availability_info/availability_field'); } + $paths[] = new restore_path_element('tag', '/module/tags/tag'); + // Apply for 'format' plugins optional paths at module level $this->add_plugin_structure('format', $module); @@ -3691,6 +3693,25 @@ protected function process_module($data) { } } + /** + * Fetch all the existing because tag_set() deletes them + * so everything must be reinserted on each call. + * + * @param stdClass $data Record data + */ + protected function process_tag($data) { + global $CFG; + + $data = (object)$data; + + if (core_tag_tag::is_enabled('core', 'course_modules')) { + $modcontext = context::instance_by_id($this->task->get_contextid()); + $instanceid = $this->task->get_moduleid(); + + core_tag_tag::add_item_tag('core', 'course_modules', $instanceid, $modcontext, $data->rawname); + } + } + /** * Process the legacy availability table record. This table does not exist * in Moodle 2.7+ but we still support restore. @@ -3759,6 +3780,20 @@ protected function process_availability_field($data) { array('id' => $availfield->coursemoduleid)); } } + /** + * This method will be executed after the rest of the restore has been processed. + * + * Update old tag instance itemid(s). + */ + protected function after_restore() { + global $DB; + + $contextid = $this->task->get_contextid(); + $instanceid = $this->task->get_activityid(); + $olditemid = $this->task->get_old_activityid(); + + $DB->set_field('tag_instance', 'itemid', $instanceid, array('contextid' => $contextid, 'itemid' => $olditemid)); + } } /** diff --git a/course/lib.php b/course/lib.php index 4cb1e52891fce..6afaae0a7e874 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1690,6 +1690,7 @@ function course_delete_module($cmid) { // Delete all tag instances associated with the instance of this module. core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id); + core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id); // Delete the context. context_helper::delete_instance(CONTEXT_MODULE, $cm->id); diff --git a/course/modedit.php b/course/modedit.php index 65880f9bcf189..27b982b39305a 100644 --- a/course/modedit.php +++ b/course/modedit.php @@ -156,6 +156,7 @@ $data->completionexpected = $cm->completionexpected; $data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1; $data->showdescription = $cm->showdescription; + $data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id); if (!empty($CFG->enableavailability)) { $data->availabilityconditionsjson = $cm->availability; } diff --git a/course/modlib.php b/course/modlib.php index b878ca1025026..56814ad910aec 100644 --- a/course/modlib.php +++ b/course/modlib.php @@ -151,6 +151,11 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) { $DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->instance)); } + // Add module tags. + if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) { + core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags); + } + // Course_modules and course_sections each contain a reference to each other. // So we have to update one of them twice. $sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section); @@ -578,6 +583,11 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) { set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber); } + // Update module tags. + if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) { + core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags); + } + // Now that module is fully updated, also update completion data if required. // (this will wipe all user completion data and recalculate it) if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) { diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index 1f493d09ffa36..f8541a1de40a8 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -597,6 +597,20 @@ function standard_coursemodule_elements(){ $mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE); } + // Populate module tags. + $categorycontext = context_coursecat::instance($COURSE->category); + $coursecontext = context_course::instance($COURSE->id); + if (core_tag_tag::is_enabled('core', 'course_modules')) { + $mform->addElement('header', 'tagshdr', get_string('tags', 'tag')); + $mform->addElement('tags', 'tags', get_string('tags'), array('itemtype' => 'course_modules', 'component' => 'core')); + if ($this->_cm) { + $modinfo = get_fast_modinfo($COURSE); + $cm = $modinfo->get_cm($this->_cm->id); + $tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id); + $mform->setDefault('tags', $tags); + } + } + $this->standard_hidden_coursemodule_elements(); } diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index de740656f85e2..5692f6ccac59f 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -1521,10 +1521,10 @@ public function test_course_delete_module($type, $options) { switch ($type) { case 'assign': // Add some tags to this assignment. - core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3')); + core_tag_tag::set_item_tags('core', 'course_modules', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3')); // Confirm the tag instances were added. - $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id); + $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id); $this->assertEquals(3, $DB->count_records('tag_instance', $criteria)); // Verify event assignment event has been generated. diff --git a/lang/en/tag.php b/lang/en/tag.php index 552cdcc6e779c..af4bd3fc3f802 100644 --- a/lang/en/tag.php +++ b/lang/en/tag.php @@ -116,6 +116,7 @@ $string['tagarea_post'] = 'Blog posts'; $string['tagarea_user'] = 'User interests'; $string['tagarea_course'] = 'Courses'; +$string['tagarea_course_modules'] = 'Course modules'; $string['tagareaenabled'] = 'Enabled'; $string['tagareaname'] = 'Name'; $string['tagareas'] = 'Tag areas'; diff --git a/lib/db/tag.php b/lib/db/tag.php index 074b685d4a24a..80bb379e87b51 100644 --- a/lib/db/tag.php +++ b/lib/db/tag.php @@ -80,4 +80,8 @@ 'itemtype' => 'blog_external', // External blogs. 'component' => 'core', ), + array( + 'itemtype' => 'course_modules', // Course modules. + 'component' => 'core', + ), ); diff --git a/lib/testing/generator/module_generator.php b/lib/testing/generator/module_generator.php index e686f6aa6d5ca..81e68a5e62913 100644 --- a/lib/testing/generator/module_generator.php +++ b/lib/testing/generator/module_generator.php @@ -248,6 +248,10 @@ public function create_instance($record = null, array $options = null) { $record->introformat = FORMAT_MOODLE; } + if (isset($record->tags) && !is_array($record->tags)) { + $record->tags = preg_split('/\s*,\s*/', trim($record->tags), -1, PREG_SPLIT_NO_EMPTY); + } + // Before Moodle 2.6 it was possible to create a module with completion tracking when // it is not setup for course and/or site-wide. Display debugging message so it is // easier to trace an error in unittests. diff --git a/lib/testing/tests/generator_test.php b/lib/testing/tests/generator_test.php index 42e07fd2bf194..0c55fab580c01 100644 --- a/lib/testing/tests/generator_test.php +++ b/lib/testing/tests/generator_test.php @@ -199,6 +199,10 @@ public function test_create_module() { $cm = get_coursemodule_from_instance('page', $page->id, $SITE->id, true); $this->assertEquals(3, $cm->sectionnum); + $page = $generator->create_module('page', array('course' => $SITE->id, 'tags' => 'Cat, Dog')); + $this->assertEquals(array('Cat', 'Dog'), + array_values(core_tag_tag::get_item_tags_array('core', 'course_modules', $page->cmid))); + // Prepare environment to generate modules with all possible options. // Enable advanced functionality.