Commit 8341055e authored by Marina Glancy's avatar Marina Glancy
Browse files

MDL-4782 course: Allow activities in the "stealth" mode

Add field 'visibleoncoursepage' to the course_modules table
Add site-wide setting for turning on stealth mode availability
Add callback for "stealth" mode support in the course formats
Change display of modules/sections availability on the course page
parent 4b6728e4
......@@ -54,4 +54,10 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$optionalsubsystems->add(new admin_setting_configcheckbox('enableglobalsearch', new lang_string('enableglobalsearch', 'admin'),
new lang_string('enableglobalsearch_desc', 'admin'), 0, 1, 0));
$choices = array();
$choices[0] = new lang_string('no');
$choices[1] = new lang_string('yes');
$optionalsubsystems->add(new admin_setting_configselect('allowstealth', new lang_string('allowstealthmodules'),
new lang_string('allowstealthmodules_help'), 0, $choices));
}
......@@ -264,7 +264,7 @@ class backup_module_structure_step extends backup_structure_step {
$module = new backup_nested_element('module', array('id', 'version'), array(
'modulename', 'sectionid', 'sectionnumber', 'idnumber',
'added', 'score', 'indent', 'visible',
'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'availability', 'showdescription'));
......
......@@ -59,7 +59,7 @@ class block_site_main_menu extends block_list {
if (!empty($modinfo->sections[0])) {
foreach($modinfo->sections[0] as $cmid) {
$cm = $modinfo->cms[$cmid];
if (!$cm->uservisible) {
if (!$cm->uservisible || !$cm->is_visible_on_course_page()) {
continue;
}
......@@ -72,7 +72,7 @@ class block_site_main_menu extends block_list {
if (!empty($cm->url)) {
$content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
} else {
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$content = $courserenderer->course_section_cm_text($cm);
}
$this->content->items[] = $indent . html_writer::div($content, 'main-menu-content');
......@@ -103,7 +103,7 @@ class block_site_main_menu extends block_list {
if (!empty($modinfo->sections[0])) {
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
if (!$mod->uservisible || !$mod->is_visible_on_course_page()) {
continue;
}
if (!$ismoving) {
......@@ -138,7 +138,7 @@ class block_site_main_menu extends block_list {
$indent = '';
}
if (!$mod->url) {
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$content = $courserenderer->course_section_cm_text($mod);
} else {
$content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
}
......
......@@ -61,12 +61,12 @@ class block_social_activities extends block_list {
if (!empty($modinfo->sections[0])) {
foreach($modinfo->sections[0] as $cmid) {
$cm = $modinfo->cms[$cmid];
if (!$cm->uservisible) {
if (!$cm->uservisible || !$cm->is_visible_on_course_page()) {
continue;
}
if (!$cm->url) {
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$content = $courserenderer->course_section_cm_text($cm);
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
......@@ -98,7 +98,7 @@ class block_social_activities extends block_list {
if (!empty($modinfo->sections[0])) {
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
if (!$mod->uservisible || !$mod->is_visible_on_course_page()) {
continue;
}
if (!$ismoving) {
......@@ -128,7 +128,7 @@ class block_social_activities extends block_list {
$this->content->icons[] = '';
}
if (!$mod->url) {
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$content = $courserenderer->course_section_cm_text($mod);
$this->content->items[] = $content . $editbuttons;
$this->content->icons[] = '';
} else {
......
......@@ -1170,6 +1170,68 @@ abstract class format_base {
$startdate = $mform->getElementValue($fieldnames['startdate']);
return $mform->getElement($fieldnames['startdate'])->exportValue($startdate);
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
return false;
}
/**
* Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide)
*
* Access to the course is already validated in the WS but the callback has to make sure
* that particular action is allowed by checking capabilities
*
* Course formats should register
*
* @param stdClass|section_info $section
* @param string $action
* @param int $sr
* @return null|array|stdClass any data for the Javascript post-processor (must be json-encodeable)
*/
public function section_action($section, $action, $sr) {
global $PAGE;
if (!$this->uses_sections() || !$section->section) {
// No section actions are allowed if course format does not support sections.
// No actions are allowed on the 0-section by default (overwrite in course format if needed).
throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));
}
$course = $this->get_course();
$coursecontext = context_course::instance($course->id);
switch($action) {
case 'hide':
case 'show':
require_capability('moodle/course:sectionvisibility', $coursecontext);
$visible = ($action === 'hide') ? 0 : 1;
course_update_section($course, $section, array('visible' => $visible));
break;
default:
throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));
}
$modules = [];
$modinfo = get_fast_modinfo($course);
$coursesections = $modinfo->sections;
if (array_key_exists($section->section, $coursesections)) {
$courserenderer = $PAGE->get_renderer('core', 'course');
$completioninfo = new completion_info($course);
foreach ($coursesections[$section->section] as $cmid) {
$cm = $modinfo->get_cm($cmid);
$modules[] = $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sr);
}
}
return ['modules' => $modules];
}
}
/**
......@@ -1231,4 +1293,16 @@ class format_site extends format_base {
}
return $courseformatoptions;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
return true;
}
}
......@@ -38,7 +38,7 @@ defined('MOODLE_INTERNAL') || die();
*/
abstract class format_section_renderer_base extends plugin_renderer_base {
/** @var contains instance of core course renderer */
/** @var core_course_renderer contains instance of core course renderer */
protected $courserenderer;
/**
......@@ -127,7 +127,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$menu->add($al);
}
$o .= html_writer::div($this->render($menu), 'section_action_menu');
$o .= html_writer::div($this->render($menu), 'section_action_menu',
array('data-sectionid' => $section->id));
}
return $o;
......@@ -194,7 +195,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
// Only in the non-general sections.
if (!$section->visible) {
$sectionstyle = ' hidden';
} else if (course_get_format($course)->is_section_current($section)) {
}
if (course_get_format($course)->is_section_current($section)) {
$sectionstyle = ' current';
}
}
......@@ -226,10 +228,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$sectionname = html_writer::tag('span', $this->section_title($section, $course));
$o.= $this->output->heading($sectionname, 3, 'sectionname' . $classes);
$context = context_course::instance($course->id);
$o .= $this->section_availability_message($section,
has_capability('moodle/course:viewhiddensections', $context));
$o .= $this->section_availability($section);
$o .= html_writer::start_tag('div', array('class' => 'summary'));
$o .= $this->format_summary_text($section);
......@@ -306,14 +305,12 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
return array();
}
$sectionreturn = $onsectionpage ? $section->section : null;
$coursecontext = context_course::instance($course->id);
$isstealth = isset($course->numsections) && ($section->section > $course->numsections);
if ($onsectionpage) {
$baseurl = course_get_url($course, $section->section);
} else {
$baseurl = course_get_url($course);
}
$baseurl = course_get_url($course, $sectionreturn);
$baseurl->param('sesskey', sesskey());
$controls = array();
......@@ -326,7 +323,6 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$streditsection = get_string('editsection');
}
$sectionreturn = $onsectionpage ? $section->section : 0;
$controls['edit'] = array(
'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)),
'icon' => 'i/settings',
......@@ -347,7 +343,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'icon' => 'i/hide',
'name' => $strhidefromothers,
'pixattr' => array('class' => '', 'alt' => $strhidefromothers),
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers));
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers,
'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide'));
} else {
$strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
$url->param('show', $section->section);
......@@ -356,7 +353,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
'icon' => 'i/show',
'name' => $strshowfromothers,
'pixattr' => array('class' => '', 'alt' => $strshowfromothers),
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers));
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers,
'data-sectionreturn' => $sectionreturn, 'data-action' => 'show'));
}
}
......@@ -399,14 +397,14 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
}
$url = new moodle_url('/course/editsection.php', array(
'id' => $section->id,
'sr' => $onsectionpage ? $section->section : 0,
'sr' => $sectionreturn,
'delete' => 1));
$controls['delete'] = array(
'url' => $url,
'icon' => 'i/delete',
'name' => $strdelete,
'pixattr' => array('class' => '', 'alt' => $strdelete),
'attr' => array('class' => 'icon delete', 'title' => $strdelete));
'attr' => array('class' => 'icon editing_delete', 'title' => $strdelete));
}
}
......@@ -453,9 +451,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
$o.= html_writer::end_tag('div');
$o.= $this->section_activity_summary($section, $course, null);
$context = context_course::instance($course->id);
$o .= $this->section_availability_message($section,
has_capability('moodle/course:viewhiddensections', $context));
$o .= $this->section_availability($section);
$o .= html_writer::end_tag('div');
$o .= html_writer::end_tag('li');
......@@ -549,31 +545,50 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
* are going to be unavailable etc). This logic is the same as for
* activities.
*
* @param stdClass $section The course_section entry from DB
* @param section_info $section The course_section entry from DB
* @param bool $canviewhidden True if user can view hidden sections
* @return string HTML to output
*/
protected function section_availability_message($section, $canviewhidden) {
global $CFG;
$o = '';
if (!$section->uservisible) {
// Note: We only get to this function if availableinfo is non-empty,
// so there is definitely something to print.
$formattedinfo = \core_availability\info::format_info(
$section->availableinfo, $section->course);
$o .= html_writer::div($formattedinfo, 'availabilityinfo');
} else if ($canviewhidden && !empty($CFG->enableavailability) && $section->visible) {
if (!$section->visible) {
if ($canviewhidden) {
$o .= $this->courserenderer->availability_info(get_string('hiddenfromstudents'), 'ishidden');
}
} else if (!$section->uservisible) {
if ($section->availableinfo) {
// Note: We only get to this function if availableinfo is non-empty,
// so there is definitely something to print.
$formattedinfo = \core_availability\info::format_info(
$section->availableinfo, $section->course);
$o .= $this->courserenderer->availability_info($formattedinfo);
}
} else if ($canviewhidden && !empty($CFG->enableavailability)) {
// Check if there is an availability restriction.
$ci = new \core_availability\info_section($section);
$fullinfo = $ci->get_full_information();
if ($fullinfo) {
$formattedinfo = \core_availability\info::format_info(
$fullinfo, $section->course);
$o .= html_writer::div($formattedinfo, 'availabilityinfo');
$o .= $this->courserenderer->availability_info($formattedinfo);
}
}
return $o;
}
/**
* Displays availability information for the section (hidden, not available unles, etc.)
*
* @param section_info $section
* @return string
*/
public function section_availability($section) {
$context = context_course::instance($section->course);
$canviewhidden = has_capability('moodle/course:viewhiddensections', $context);
return html_writer::div($this->section_availability_message($section, $canviewhidden), 'section_availability');
}
/**
* Show if something is on on the course clipboard (moving around)
*
......
......@@ -108,4 +108,16 @@ class format_social extends format_base {
}
return $courseformatoptions;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
return true;
}
}
......@@ -410,6 +410,36 @@ class format_topics extends format_base {
public function supports_news() {
return true;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
// Allow the third visibility state inside visible sections or in section 0, not allow in orphaned sections.
return !$section->section || ($section->visible && $section->section <= $this->get_course()->numsections);
}
public function section_action($section, $action, $sr) {
global $PAGE;
if ($section->section && ($action === 'setmarker' || $action === 'removemarker')) {
// Format 'topics' allows to set and remove markers in addition to common section actions.
require_capability('moodle/course:setcurrentsection', context_course::instance($this->courseid));
course_set_marker($this->courseid, ($action === 'setmarker') ? $section->section : 0);
return null;
}
// For show/hide actions call the parent method and return the new content for .section_availability element.
$rv = parent::section_action($section, $action, $sr);
$renderer = $PAGE->get_renderer('format_topics');
$rv['section_availability'] = $renderer->section_availability($this->get_section($section));
return $rv;
}
}
/**
......
......@@ -129,7 +129,8 @@ class format_topics_renderer extends format_section_renderer_base {
$controls['highlight'] = array('url' => $url, "icon" => 'i/marked',
'name' => $highlightoff,
'pixattr' => array('class' => '', 'alt' => $markedthistopic),
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic));
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic,
'data-action' => 'removemarker'));
} else {
$url->param('marker', $section->section);
$markthistopic = get_string('markthistopic');
......@@ -137,7 +138,8 @@ class format_topics_renderer extends format_section_renderer_base {
$controls['highlight'] = array('url' => $url, "icon" => 'i/marker',
'name' => $highlight,
'pixattr' => array('class' => '', 'alt' => $markthistopic),
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic));
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic,
'data-action' => 'setmarker'));
}
}
......
......@@ -2,6 +2,15 @@ This files describes API changes for course formats
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
=== 3.3 ===
* Javascript code for editing activities and sections was moved to an AMD module, course/rest.php is no longer
responsible for editing actions, instead it is done in web services. Carefully test all editing actions during upgrade.
* The new method format_base::allow_stealth_module_visibility() can indicate whether course format supports "stealth"
activities mode when they are available but not visible on course page. Course format that supports stealth mode
must check $cm->is_visible_on_course_page() when displaying activities list on the course page instead of $cm->uservisible.
For all other plugins except course formats the same property $cm->uservisible indicates if the activity contents
is actually available to student.
=== 3.2 ===
* Callback delete_course is deprecated and should be replaced with observer for event \core\event\course_content_deleted
* Course formats can overwrite get_default_course_enddate function to set the default course end date for new courses.
......
......@@ -497,6 +497,29 @@ class format_weeks extends format_base {
public function supports_news() {
return true;
}
/**
* Returns whether this course format allows the activity to
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
*
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
* @param stdClass|section_info $section section where this module is located or will be added to
* @return bool
*/
public function allow_stealth_module_visibility($cm, $section) {
// Allow the third visibility state inside visible sections or in section 0, not allow in orphaned sections.
return !$section->section || ($section->visible && $section->section <= $this->get_course()->numsections);
}
public function section_action($section, $action, $sr) {
global $PAGE;
// Call the parent method and return the new content for .section_availability element.
$rv = parent::section_action($section, $action, $sr);
$renderer = $PAGE->get_renderer('format_weeks');
$rv['section_availability'] = $renderer->section_availability($this->get_section($section));
return $rv;
}
}
/**
......
</
......@@ -388,13 +388,16 @@ function get_array_of_activities($courseid) {
if (empty($rawmods)) {
return $mod; // always return array
}
$courseformat = course_get_format($course);
if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
if ($sections = $DB->get_records('course_sections', array('course' => $courseid),
'section ASC', 'id,section,sequence,visible')) {
// First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
debugging(join('<br>', $errormessages));
$rawmods = get_course_mods($courseid);
$sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
$sections = $DB->get_records('course_sections', array('course' => $courseid),
'section ASC', 'id,section,sequence,visible');
}
// Build array of activities.
foreach ($sections as $section) {
......@@ -404,6 +407,13 @@ function get_array_of_activities($courseid) {
if (empty($rawmods[$seq])) {
continue;
}
// Adjust visibleoncoursepage, value in DB may not respect format availability.
$rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible
|| $rawmods[$seq]->visibleoncoursepage
|| empty($CFG->allowstealth)
|| !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0;
// Create an object that will be cached.
$mod[$seq] = new stdClass();
$mod[$seq]->id = $rawmods[$seq]->instance;
$mod[$seq]->cm = $rawmods[$seq]->id;
......@@ -418,6 +428,7 @@ function get_array_of_activities($courseid) {
$mod[$seq]->score = $rawmods[$seq]->score;
$mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
$mod[$seq]->visible = $rawmods[$seq]->visible;
$mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage;
$mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
$mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
$mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
......@@ -557,9 +568,15 @@ function get_module_types_names($plural = false) {
* @return void
*/
function course_set_marker($courseid, $marker) {
global $DB;
global $DB, $COURSE;
$DB->set_field("course", "marker", $marker, array('id' => $courseid));
format_base::reset_course_cache($courseid);
if ($COURSE && $COURSE->id == $courseid) {
$COURSE->marker = $marker;
}
if (class_exists('format_base')) {
format_base::reset_course_cache($courseid);
}
course_modinfo::clear_instance_cache($courseid);
}
/**
......@@ -950,33 +967,29 @@ function set_coursemodule_idnumber($id, $idnumber) {
*
* @param int $id of the module
* @param int $visible state of the module
* @param int $visibleoncoursepage state of the module on the course page
* @return bool false when the module was not found, true otherwise
*/
function set_coursemodule_visible($id, $visible) {
function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
global $DB, $CFG;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
// Trigger developer's attention when using the previously removed argument.
if (func_num_args() > 2) {
debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
has been removed.', DEBUG_DEVELOPER);
}
if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
return false;
}
// Create events and propagate visibility to associated grade items if the value has changed.
// Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
if ($cm->visible == $visible) {
if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
return true;
}
if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
return false;
}
if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
if (($cm->visible != $visible) &&
($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename)))) {
foreach($events as $event) {
if ($visible) {
$event = new calendar_event($event);
......@@ -993,17 +1006,19 @@ function set_coursemodule_visible($id, $visible) {
$cminfo = new stdClass();
$cminfo->id = $id;
$cminfo->visible = $visible;
$cminfo->visibleoncoursepage = $visibleoncoursepage;
$cminfo->visibleold = $visible;
$DB->update_record('course_modules', $cminfo);
// Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
// Note that this must be done after updating the row in course_modules, in case
// the modules grade_item_update function needs to access $cm->visible.
if (plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
if ($cm->visible != $visible &&
plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
$instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
} else {
} else if ($cm->visible != $visible) {
$grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
if ($grade_items) {
foreach ($grade_items as $grade_item) {
......@@ -1593,10 +1608,10 @@ function course_update_section($course, $section, $data) {
if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
if ($data['visible']) {
// As we unhide the section, we use the previously saved visibility stored in visibleold.
set_coursemodule_visible($moduleid, $cm->visibleold);
set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage);
} else {
// We hide the section, so we hide the module but we store the original state in visibleold.
set_coursemodule_visible($moduleid, 0);
set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage);
$DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
}
\core\event\course_module_updated::create_from_cm($cm)->trigger();
......@@ -1765,12 +1780,13 @@ function moveto_module($mod, $section, $beforemod=NULL) {