Merge branch 'MDL-55956-master-5' of git://github.com/junpataleta/moodle

This commit is contained in:
Andrew Nicols 2017-03-07 12:07:50 +08:00
commit 342af35ab8
19 changed files with 864 additions and 174 deletions

View file

@ -120,6 +120,11 @@ define('CALENDAR_SUBSCRIPTION_UPDATE', 1);
*/ */
define('CALENDAR_SUBSCRIPTION_REMOVE', 2); define('CALENDAR_SUBSCRIPTION_REMOVE', 2);
/**
* CALENDAR_EVENT_USER_OVERRIDE_PRIORITY - Constant for the user override priority.
*/
define('CALENDAR_EVENT_USER_OVERRIDE_PRIORITY', 9999999);
/** /**
* Return the days of the week * Return the days of the week
* *
@ -723,85 +728,164 @@ function calendar_add_event_metadata($event) {
* @param boolean $ignorehidden whether to select only visible events or all events * @param boolean $ignorehidden whether to select only visible events or all events
* @return array $events of selected events or an empty array if there aren't any (or there was an error) * @return array $events of selected events or an empty array if there aren't any (or there was an error)
*/ */
function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration=true, $ignorehidden=true) { function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration = true, $ignorehidden = true) {
global $DB; global $DB;
$whereclause = '';
$params = array(); $params = array();
// Quick test. // Quick test.
if (empty($users) && empty($groups) && empty($courses)) { if (empty($users) && empty($groups) && empty($courses)) {
return array(); return array();
} }
// Array of filter conditions. To be concatenated by the OR operator.
$filters = [];
// User filter.
if ((is_array($users) && !empty($users)) or is_numeric($users)) { if ((is_array($users) && !empty($users)) or is_numeric($users)) {
// Events from a number of users // Events from a number of users
if(!empty($whereclause)) $whereclause .= ' OR';
list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED); list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
$whereclause .= " (e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0)"; $filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0)";
$params = array_merge($params, $inparamsusers); $params = array_merge($params, $inparamsusers);
} else if($users === true) { } else if ($users === true) {
// Events from ALL users // Events from ALL users
if(!empty($whereclause)) $whereclause .= ' OR'; $filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0)";
$whereclause .= ' (e.userid != 0 AND e.courseid = 0 AND e.groupid = 0)';
} else if($users === false) {
// No user at all, do nothing
} }
// Boolean false (no users at all): We don't need to do anything.
// Group filter.
if ((is_array($groups) && !empty($groups)) or is_numeric($groups)) { if ((is_array($groups) && !empty($groups)) or is_numeric($groups)) {
// Events from a number of groups // Events from a number of groups
if(!empty($whereclause)) $whereclause .= ' OR';
list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED); list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
$whereclause .= " e.groupid $insqlgroups "; $filters[] = "e.groupid $insqlgroups";
$params = array_merge($params, $inparamsgroups); $params = array_merge($params, $inparamsgroups);
} else if($groups === true) { } else if ($groups === true) {
// Events from ALL groups // Events from ALL groups
if(!empty($whereclause)) $whereclause .= ' OR '; $filters[] = "e.groupid != 0";
$whereclause .= ' e.groupid != 0';
} }
// boolean false (no groups at all): we don't need to do anything // Boolean false (no groups at all): We don't need to do anything.
// Course filter.
if ((is_array($courses) && !empty($courses)) or is_numeric($courses)) { if ((is_array($courses) && !empty($courses)) or is_numeric($courses)) {
if(!empty($whereclause)) $whereclause .= ' OR';
list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED); list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
$whereclause .= " (e.groupid = 0 AND e.courseid $insqlcourses)"; $filters[] = "(e.groupid = 0 AND e.courseid $insqlcourses)";
$params = array_merge($params, $inparamscourses); $params = array_merge($params, $inparamscourses);
} else if ($courses === true) { } else if ($courses === true) {
// Events from ALL courses // Events from ALL courses
if(!empty($whereclause)) $whereclause .= ' OR'; $filters[] = "(e.groupid = 0 AND e.courseid != 0)";
$whereclause .= ' (e.groupid = 0 AND e.courseid != 0)';
} }
// Security check: if, by now, we have NOTHING in $whereclause, then it means // Security check: if, by now, we have NOTHING in $whereclause, then it means
// that NO event-selecting clauses were defined. Thus, we won't be returning ANY // that NO event-selecting clauses were defined. Thus, we won't be returning ANY
// events no matter what. Allowing the code to proceed might return a completely // events no matter what. Allowing the code to proceed might return a completely
// valid query with only time constraints, thus selecting ALL events in that time frame! // valid query with only time constraints, thus selecting ALL events in that time frame!
if(empty($whereclause)) { if (empty($filters)) {
return array(); return array();
} }
if($withduration) { // Build our clause for the filters.
$timeclause = '(e.timestart >= '.$tstart.' OR e.timestart + e.timeduration > '.$tstart.') AND e.timestart <= '.$tend; $filterclause = implode(' OR ', $filters);
}
else {
$timeclause = 'e.timestart >= '.$tstart.' AND e.timestart <= '.$tend;
}
if(!empty($whereclause)) {
// We have additional constraints
$whereclause = $timeclause.' AND ('.$whereclause.')';
}
else {
// Just basic time filtering
$whereclause = $timeclause;
}
// Array of where conditions for our query. To be concatenated by the AND operator.
$whereconditions = ["($filterclause)"];
// Time clause.
if ($withduration) {
$timeclause = "((e.timestart >= :tstart1 OR e.timestart + e.timeduration > :tstart2) AND e.timestart <= :tend)";
$params['tstart1'] = $tstart;
$params['tstart2'] = $tstart;
$params['tend'] = $tend;
} else {
$timeclause = "(e.timestart >= :tstart AND e.timestart <= :tend)";
$params['tstart'] = $tstart;
$params['tend'] = $tend;
}
$whereconditions[] = $timeclause;
// Show visible only.
if ($ignorehidden) { if ($ignorehidden) {
$whereclause .= ' AND e.visible = 1'; $whereconditions[] = "(e.visible = 1)";
} }
// Build the main query's WHERE clause.
$whereclause = implode(' AND ', $whereconditions);
// Build SQL subquery and conditions for filtered events based on priorities.
$subquerywhere = '';
$subqueryconditions = [];
// Get the user's courses. Otherwise, get the default courses being shown by the calendar.
$usercourses = calendar_get_default_courses();
// Set calendar filters.
list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true);
$subqueryparams = [];
// Flag to indicate whether the query needs to exclude group overrides.
$viewgroupsonly = false;
if ($user) {
// Set filter condition for the user's events.
$subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0)";
$subqueryparams['user'] = $user;
foreach ($usercourses as $courseid) {
if (has_capability('moodle/site:accessallgroups', context_course::instance($courseid))) {
$usergroupmembership = groups_get_all_groups($courseid, $user, 0, 'g.id');
if (count($usergroupmembership) == 0) {
$viewgroupsonly = true;
break;
}
}
}
}
// Set filter condition for the user's group events.
if ($usergroups === true || $viewgroupsonly) {
// Fetch group events, but not group overrides.
$subqueryconditions[] = "(ev.groupid != 0 AND ev.eventtype = 'group')";
} else if (!empty($usergroups)) {
// Fetch group events and group overrides.
list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.groupid $inusergroups)";
$subqueryparams = array_merge($subqueryparams, $inusergroupparams);
}
// Set filter condition for the user's courses.
if (!empty($usercourses)) {
list($inusercourses, $inusercoursesparams) = $DB->get_in_or_equal($usercourses, SQL_PARAMS_NAMED);
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $inusercourses)";
$subqueryparams = array_merge($subqueryparams, $inusercoursesparams);
}
// Build the WHERE condition for the sub-query.
if (!empty($subqueryconditions)) {
$subquerywhere = 'WHERE ' . implode(" OR ", $subqueryconditions);
}
// Merge subquery parameters to the parameters of the main query.
if (!empty($subqueryparams)) {
$params = array_merge($params, $subqueryparams);
}
// Sub-query that fetches the list of unique events that were filtered based on priority.
$subquery = "SELECT ev.modulename,
ev.instance,
ev.eventtype,
MAX(ev.priority) as priority
FROM {event} ev
$subquerywhere
GROUP BY ev.modulename, ev.instance, ev.eventtype";
// Build the main query.
$sql = "SELECT e.* $sql = "SELECT e.*
FROM {event} e FROM {event} e
LEFT JOIN {modules} m ON e.modulename = m.name INNER JOIN ($subquery) fe
-- Non visible modules will have a value of 0. ON e.modulename = fe.modulename
AND e.instance = fe.instance
AND e.eventtype = fe.eventtype
AND (e.priority = fe.priority OR (e.priority IS NULL AND fe.priority IS NULL))
LEFT JOIN {modules} m
ON e.modulename = m.name
WHERE (m.visible = 1 OR m.visible IS NULL) AND $whereclause WHERE (m.visible = 1 OR m.visible IS NULL) AND $whereclause
ORDER BY e.timestart"; ORDER BY e.timestart";
$events = $DB->get_records_sql($sql, $params); $events = $DB->get_records_sql($sql, $params);

View file

@ -62,7 +62,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
*/ */
public static function create_calendar_event($name, $userid = 0, $type = 'user', $repeats = 0, $timestart = null, $prop = null) { public static function create_calendar_event($name, $userid = 0, $type = 'user', $repeats = 0, $timestart = null, $prop = null) {
global $CFG, $DB, $USER, $SITE; global $CFG, $DB, $SITE;
require_once("$CFG->dirroot/calendar/lib.php"); require_once("$CFG->dirroot/calendar/lib.php");
if (!empty($prop)) { if (!empty($prop)) {
@ -100,6 +100,26 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
if (!isset($prop->courseid)) { if (!isset($prop->courseid)) {
$prop->courseid = $SITE->id; $prop->courseid = $SITE->id;
} }
// Determine event priority.
if ($prop->courseid == 0 && isset($prop->groupid) && $prop->groupid == 0 && !empty($prop->userid)) {
// User override event.
$prop->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
} else if ($prop->courseid != $SITE->id && !empty($prop->groupid)) {
// Group override event.
$priorityparams = ['courseid' => $prop->courseid, 'groupid' => $prop->groupid];
// Group override event with the highest priority.
$groupevents = $DB->get_records('event', $priorityparams, 'priority DESC', 'id, priority', 0, 1);
$priority = 1;
if (!empty($groupevents)) {
$event = reset($groupevents);
if (!empty($event->priority)) {
$priority = $event->priority + 1;
}
}
$prop->priority = $priority;
}
$event = new calendar_event($prop); $event = new calendar_event($prop);
return $event->create($prop); return $event->create($prop);
} }
@ -266,6 +286,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
global $DB, $USER; global $DB, $USER;
$this->resetAfterTest(true); $this->resetAfterTest(true);
set_config('calendar_adminseesall', 1);
$this->setAdminUser(); $this->setAdminUser();
// Create a few stuff to test with. // Create a few stuff to test with.
@ -296,13 +317,19 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$record = new stdClass(); $record = new stdClass();
$record->courseid = $course->id; $record->courseid = $course->id;
$record->groupid = 0;
$record->description = array( $record->description = array(
'format' => FORMAT_HTML, 'format' => FORMAT_HTML,
'text' => 'Text with img <img src="@@PLUGINFILE@@/fakeimage.png">', 'text' => 'Text with img <img src="@@PLUGINFILE@@/fakeimage.png">',
'itemid' => $draftidfile 'itemid' => $draftidfile
); );
$courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record); $courseevent = $this->create_calendar_event('course', $USER->id, 'course', 2, time(), $record);
$userevent = $this->create_calendar_event('user', $USER->id);
$record = new stdClass();
$record->courseid = 0;
$record->groupid = 0;
$userevent = $this->create_calendar_event('user', $USER->id, 'user', 0, time(), $record);
$record = new stdClass(); $record = new stdClass();
$record->courseid = $course->id; $record->courseid = $course->id;
$record->groupid = $group->id; $record->groupid = $group->id;
@ -335,6 +362,13 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$this->assertEquals(2, $withdescription); $this->assertEquals(2, $withdescription);
// Let's play around with caps. // Let's play around with caps.
// Create user event for the user $user.
$record = new stdClass();
$record->courseid = 0;
$record->groupid = 0;
$this->create_calendar_event('user', $user->id, 'user', 0, time(), $record);
$this->setUser($user); $this->setUser($user);
$events = core_calendar_external::get_calendar_events($paramevents, $options); $events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events); $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);

View file

@ -119,35 +119,42 @@ class core_calendar_lib_testcase extends advanced_testcase {
public function test_calendar_get_events_with_disabled_module() { public function test_calendar_get_events_with_disabled_module() {
global $DB; global $DB;
$course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator();
$events = [[ $course = $generator->create_course();
'name' => 'Start of assignment', $student = $generator->create_user();
'description' => '', $generator->enrol_user($student->id, $course->id, 'student');
'format' => 1, $this->setUser($student);
'courseid' => $course->id,
'groupid' => 0, $events = [
'userid' => 2, [
'modulename' => 'assign', 'name' => 'Start of assignment',
'instance' => 1, 'description' => '',
'eventtype' => 'due', 'format' => 1,
'timestart' => time(), 'courseid' => $course->id,
'timeduration' => 86400, 'groupid' => 0,
'visible' => 1 'userid' => 2,
], [ 'modulename' => 'assign',
'name' => 'Start of lesson', 'instance' => 1,
'description' => '', 'eventtype' => 'due',
'format' => 1, 'timestart' => time(),
'courseid' => $course->id, 'timeduration' => 86400,
'groupid' => 0, 'visible' => 1
'userid' => 2, ], [
'modulename' => 'lesson',
'instance' => 1, 'name' => 'Start of lesson',
'eventtype' => 'end', 'description' => '',
'timestart' => time(), 'format' => 1,
'timeduration' => 86400, 'courseid' => $course->id,
'visible' => 1 'groupid' => 0,
] 'userid' => 2,
]; 'modulename' => 'lesson',
'instance' => 1,
'eventtype' => 'end',
'timestart' => time(),
'timeduration' => 86400,
'visible' => 1
]
];
foreach ($events as $event) { foreach ($events as $event) {
calendar_event::create($event, false); calendar_event::create($event, false);
@ -171,4 +178,186 @@ class core_calendar_lib_testcase extends advanced_testcase {
$event = reset($events); $event = reset($events);
$this->assertEquals('assign', $event->modulename); $this->assertEquals('assign', $event->modulename);
} }
/**
* Test for calendar_get_events() when there are user and group overrides.
*/
public function test_calendar_get_events_with_overrides() {
global $DB;
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
if (!isset($params['course'])) {
$params['course'] = $course->id;
}
$instance = $plugingenerator->create_instance($params);
// Create users.
$useroverridestudent = $generator->create_user();
$group1student = $generator->create_user();
$group2student = $generator->create_user();
$group12student = $generator->create_user();
$nogroupstudent = $generator->create_user();
// Enrol users.
$generator->enrol_user($useroverridestudent->id, $course->id, 'student');
$generator->enrol_user($group1student->id, $course->id, 'student');
$generator->enrol_user($group2student->id, $course->id, 'student');
$generator->enrol_user($group12student->id, $course->id, 'student');
$generator->enrol_user($nogroupstudent->id, $course->id, 'student');
// Create groups.
$group1 = $generator->create_group(['courseid' => $course->id]);
$group2 = $generator->create_group(['courseid' => $course->id]);
// Add members to groups.
$generator->create_group_member(['groupid' => $group1->id, 'userid' => $group1student->id]);
$generator->create_group_member(['groupid' => $group2->id, 'userid' => $group2student->id]);
$generator->create_group_member(['groupid' => $group1->id, 'userid' => $group12student->id]);
$generator->create_group_member(['groupid' => $group2->id, 'userid' => $group12student->id]);
$now = time();
// Events with the same module name, instance and event type.
$events = [
[
'name' => 'Assignment 1 due date',
'description' => '',
'format' => 0,
'courseid' => $course->id,
'groupid' => 0,
'userid' => 2,
'modulename' => 'assign',
'instance' => $instance->id,
'eventtype' => 'due',
'timestart' => $now,
'timeduration' => 0,
'visible' => 1
], [
'name' => 'Assignment 1 due date - User override',
'description' => '',
'format' => 1,
'courseid' => 0,
'groupid' => 0,
'userid' => $useroverridestudent->id,
'modulename' => 'assign',
'instance' => $instance->id,
'eventtype' => 'due',
'timestart' => $now + 86400,
'timeduration' => 0,
'visible' => 1,
'priority' => CALENDAR_EVENT_USER_OVERRIDE_PRIORITY
], [
'name' => 'Assignment 1 due date - Group A override',
'description' => '',
'format' => 1,
'courseid' => $course->id,
'groupid' => $group1->id,
'userid' => 2,
'modulename' => 'assign',
'instance' => $instance->id,
'eventtype' => 'due',
'timestart' => $now + (2 * 86400),
'timeduration' => 0,
'visible' => 1,
'priority' => 1,
], [
'name' => 'Assignment 1 due date - Group B override',
'description' => '',
'format' => 1,
'courseid' => $course->id,
'groupid' => $group2->id,
'userid' => 2,
'modulename' => 'assign',
'instance' => $instance->id,
'eventtype' => 'due',
'timestart' => $now + (3 * 86400),
'timeduration' => 0,
'visible' => 1,
'priority' => 2,
],
];
foreach ($events as $event) {
calendar_event::create($event, false);
}
$timestart = $now - 100;
$timeend = $now + (3 * 86400);
$groups = [$group1->id, $group2->id];
// Get user override events.
$this->setUser($useroverridestudent);
$events = calendar_get_events($timestart, $timeend, $useroverridestudent->id, $groups, $course->id);
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals('Assignment 1 due date - User override', $event->name);
// Get event for user with override but with the timestart and timeend parameters only covering the original event.
$events = calendar_get_events($timestart, $now, $useroverridestudent->id, $groups, $course->id);
$this->assertCount(0, $events);
// Get events for user that does not belong to any group and has no user override events.
$this->setUser($nogroupstudent);
$events = calendar_get_events($timestart, $timeend, $nogroupstudent->id, $groups, $course->id);
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals('Assignment 1 due date', $event->name);
// Get events for user that belongs to groups A and B and has no user override events.
$this->setUser($group12student);
$events = calendar_get_events($timestart, $timeend, $group12student->id, $groups, $course->id);
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals('Assignment 1 due date - Group B override', $event->name);
// Get events for user that belongs to group A and has no user override events.
$this->setUser($group1student);
$events = calendar_get_events($timestart, $timeend, $group1student->id, $groups, $course->id);
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals('Assignment 1 due date - Group A override', $event->name);
// Add repeating events.
$repeatingevents = [
[
'name' => 'Repeating site event',
'description' => '',
'format' => 1,
'courseid' => SITEID,
'groupid' => 0,
'userid' => 2,
'repeatid' => 1,
'modulename' => '0',
'instance' => 0,
'eventtype' => 'site',
'timestart' => $now + 86400,
'timeduration' => 0,
'visible' => 1,
],
[
'name' => 'Repeating site event',
'description' => '',
'format' => 1,
'courseid' => SITEID,
'groupid' => 0,
'userid' => 2,
'repeatid' => 1,
'modulename' => '0',
'instance' => 0,
'eventtype' => 'site',
'timestart' => $now + (2 * 86400),
'timeduration' => 0,
'visible' => 1,
],
];
foreach ($repeatingevents as $event) {
calendar_event::create($event, false);
}
// Make sure repeating events are not filtered out.
$events = calendar_get_events($timestart, $timeend, true, true, true);
$this->assertCount(3, $events);
}
} }

View file

@ -0,0 +1,69 @@
<?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/>.
/**
* Adhoc task that updates all of the existing calendar events for modules that implement the *_refresh_events() hook.
*
* @package core
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\task;
use core_plugin_manager;
defined('MOODLE_INTERNAL') || die();
/**
* Class that updates all of the existing calendar events for modules that implement the *_refresh_events() hook.
*
* Custom data accepted:
* - plugins -> Array of plugin names that need to be refreshed. Optional.
*
* @package core
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class refresh_mod_calendar_events_task extends adhoc_task {
/**
* Run the task to refresh calendar events.
*/
public function execute() {
// Specific list of plugins that need to be refreshed. If not set, then all mod plugins will be refreshed.
$pluginstorefresh = null;
if (isset($this->get_custom_data()->plugins)) {
$pluginstorefresh = $this->get_custom_data()->plugins;
}
$pluginmanager = core_plugin_manager::instance();
$modplugins = $pluginmanager->get_plugins_of_type('mod');
foreach ($modplugins as $plugin) {
// Check if a specific list of plugins is defined and check if it contains the plugin that is currently being evaluated.
if (!empty($pluginstorefresh) && !in_array($plugin->name, $pluginstorefresh)) {
// This plugin is not in the list, move on to the next one.
continue;
}
// Check if the plugin implements *_refresh_events() and call it when it does.
$refresheventsfunction = $plugin->name . '_refresh_events';
if (function_exists($refresheventsfunction)) {
mtrace('Calling ' . $refresheventsfunction);
call_user_func($refresheventsfunction);
}
}
}
}

View file

@ -439,6 +439,7 @@
<FIELD NAME="sequence" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/> <FIELD NAME="sequence" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="timemodified" 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="subscriptionid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The event_subscription id this event is associated with."/> <FIELD NAME="subscriptionid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The event_subscription id this event is associated with."/>
<FIELD NAME="priority" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The event's display priority. For multiple events with the same module name, instance and eventtype (e.g. for group overrides), the one with the higher priority will be displayed."/>
</FIELDS> </FIELDS>
<KEYS> <KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/> <KEY NAME="primary" TYPE="primary" FIELDS="id"/>

View file

@ -2555,5 +2555,29 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017021400.00); upgrade_main_savepoint(true, 2017021400.00);
} }
if ($oldversion < 2017030700.00) {
// Define field priority to be added to event.
$table = new xmldb_table('event');
$field = new xmldb_field('priority', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'subscriptionid');
// Conditionally launch add field priority.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Create adhoc task for upgrading of existing calendar events.
$record = new \stdClass();
$record->classname = "\\core\\task\\refresh_mod_calendar_events_task";
$record->component = 'core';
// Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
$nextruntime = time() - 1;
$record->nextruntime = $nextruntime;
$DB->insert_record('task_adhoc', $record);
// Main savepoint reached.
upgrade_main_savepoint(true, 2017030700.00);
}
return true; return true;
} }

View file

@ -53,6 +53,8 @@ information provided here is intended especially for developers.
* get_user_capability_course() now has an additional parameter 'limit'. This can be used to return a set number of records with * get_user_capability_course() now has an additional parameter 'limit'. This can be used to return a set number of records with
the submitted capability. The parameter 'fieldsexceptid' will now accept context fields which can be used for preloading. the submitted capability. The parameter 'fieldsexceptid' will now accept context fields which can be used for preloading.
* The caching option 'immutable' has been added to send_stored_file() and send_file(). * The caching option 'immutable' has been added to send_stored_file() and send_file().
* New adhoc task refresh_mod_calendar_events_task that updates existing calendar events of modules.
* New 'priority' column for the event table to determine which event to show in case of events with user and group overrides.
=== 3.2 === === 3.2 ===

View file

@ -233,5 +233,25 @@ function xmldb_assign_upgrade($oldversion) {
// Automatically generated Moodle v3.2.0 release upgrade line. // Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this. // Put any upgrade step following this.
if ($oldversion < 2017021500) {
// Fix event types of assign events.
$params = [
'modulename' => 'assign',
'eventtype' => 'close'
];
$select = "modulename = :modulename AND eventtype = :eventtype";
$DB->set_field_select('event', 'eventtype', 'due', $select, $params);
// Delete 'open' events.
$params = [
'modulename' => 'assign',
'eventtype' => 'open'
];
$DB->delete_records('event', $params);
// Assign savepoint reached.
upgrade_mod_savepoint(true, 2017021500, 'assign');
}
return true; return true;
} }

View file

@ -226,10 +226,9 @@ function assign_update_events($assign, $override = null) {
} }
$oldevents = $DB->get_records('event', $conds); $oldevents = $DB->get_records('event', $conds);
// Now make a todo list of all that needs to be updated. // Now make a to-do list of all that needs to be updated.
if (empty($override)) { if (empty($override)) {
// We are updating the primary settings for the assign, so we // We are updating the primary settings for the assign, so we need to add all the overrides.
// need to add all the overrides.
$overrides = $DB->get_records('assign_overrides', array('assignid' => $assign->id)); $overrides = $DB->get_records('assign_overrides', array('assignid' => $assign->id));
// As well as the original assign (empty override). // As well as the original assign (empty override).
$overrides[] = new stdClass(); $overrides[] = new stdClass();
@ -241,12 +240,9 @@ function assign_update_events($assign, $override = null) {
foreach ($overrides as $current) { foreach ($overrides as $current) {
$groupid = isset($current->groupid) ? $current->groupid : 0; $groupid = isset($current->groupid) ? $current->groupid : 0;
$userid = isset($current->userid) ? $current->userid : 0; $userid = isset($current->userid) ? $current->userid : 0;
$allowsubmissionsfromdate = isset($current->allowsubmissionsfromdate
) ? $current->allowsubmissionsfromdate : $assign->get_context()->allowsubmissionsfromdate;
$duedate = isset($current->duedate) ? $current->duedate : $assign->get_context()->duedate; $duedate = isset($current->duedate) ? $current->duedate : $assign->get_context()->duedate;
// Only add open/close events for an override if they differ from the assign default. // Only add 'due' events for an override if they differ from the assign default.
$addopen = empty($current->id) || !empty($current->allowsubmissionsfromdate);
$addclose = empty($current->id) || !empty($current->duedate); $addclose = empty($current->id) || !empty($current->duedate);
if (!empty($assign->coursemodule)) { if (!empty($assign->coursemodule)) {
@ -263,13 +259,14 @@ function assign_update_events($assign, $override = null) {
$event->userid = $userid; $event->userid = $userid;
$event->modulename = 'assign'; $event->modulename = 'assign';
$event->instance = $assign->get_context()->id; $event->instance = $assign->get_context()->id;
$event->timestart = $allowsubmissionsfromdate; $event->timestart = $duedate;
$event->timeduration = max($duedate - $allowsubmissionsfromdate, 0); $event->timeduration = 0;
$event->visible = instance_is_visible('assign', $assign); $event->visible = instance_is_visible('assign', $assign);
$event->eventtype = 'open'; $event->eventtype = 'due';
// Determine the event name. // Determine the event name and priority.
if ($groupid) { if ($groupid) {
// Group override event.
$params = new stdClass(); $params = new stdClass();
$params->assign = $assign->get_context()->name; $params->assign = $assign->get_context()->name;
$params->group = groups_get_group_name($groupid); $params->group = groups_get_group_name($groupid);
@ -278,49 +275,32 @@ function assign_update_events($assign, $override = null) {
continue; continue;
} }
$eventname = get_string('overridegroupeventname', 'assign', $params); $eventname = get_string('overridegroupeventname', 'assign', $params);
// Set group override priority.
if (isset($current->sortorder)) {
$event->priority = $current->sortorder;
}
} else if ($userid) { } else if ($userid) {
// User override event.
$params = new stdClass(); $params = new stdClass();
$params->assign = $assign->get_context()->name; $params->assign = $assign->get_context()->name;
$eventname = get_string('overrideusereventname', 'assign', $params); $eventname = get_string('overrideusereventname', 'assign', $params);
// Set user override priority.
$event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
} else { } else {
// The parent event.
$eventname = $assign->name; $eventname = $assign->name;
} }
if ($addopen or $addclose) {
if ($duedate and $allowsubmissionsfromdate and $event->timeduration <= ASSIGN_MAX_EVENT_LENGTH) { if ($duedate && $addclose) {
// Single event for the whole assign. if ($oldevent = array_shift($oldevents)) {
if ($oldevent = array_shift($oldevents)) { $event->id = $oldevent->id;
$event->id = $oldevent->id;
} else {
unset($event->id);
}
$event->name = $eventname;
// The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event);
} else { } else {
// Separate start and end events. unset($event->id);
$event->timeduration = 0;
if ($allowsubmissionsfromdate && $addopen) {
if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id;
} else {
unset($event->id);
}
$event->name = $eventname.' ('.get_string('open', 'assign').')';
// The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event);
}
if ($duedate && $addclose) {
if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id;
} else {
unset($event->id);
}
$event->name = $eventname.' ('.get_string('duedate', 'assign').')';
$event->timestart = $duedate;
$event->eventtype = 'close';
calendar_event::create($event);
}
} }
$event->name = $eventname.' ('.get_string('duedate', 'assign').')';
$event->timestart = $duedate;
$event->eventtype = 'due';
calendar_event::create($event);
} }
} }

View file

@ -1160,8 +1160,14 @@ class assign {
if ($instance->duedate) { if ($instance->duedate) {
$event = new stdClass(); $event = new stdClass();
// Fetch the original due date event. It will have a non-zero course ID and a zero group ID.
$select = "modulename = :modulename
AND instance = :instance
AND eventtype = :eventtype
AND groupid = 0
AND courseid <> 0";
$params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype); $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype);
$event->id = $DB->get_field('event', 'id', $params); $event->id = $DB->get_field_select('event', 'id', $select, $params);
$event->name = $instance->name; $event->name = $instance->name;
$event->timestart = $instance->duedate; $event->timestart = $instance->duedate;
@ -8822,6 +8828,14 @@ function reorder_group_overrides($assignid) {
$f->id = $override->id; $f->id = $override->id;
$f->sortorder = $i++; $f->sortorder = $i++;
$DB->update_record('assign_overrides', $f); $DB->update_record('assign_overrides', $f);
// Update priorities of group overrides.
$params = [
'modulename' => 'assign',
'instance' => $override->assignid,
'groupid' => $override->groupid
];
$DB->set_field('event', 'priority', $f->sortorder, $params);
} }
} }
} }

View file

@ -1,5 +1,10 @@
This files describes API changes in the assign code. This files describes API changes in the assign code.
=== 3.3 ===
* Fixed calendar event types for overridden due dates from 'close' to 'due'.
* Removed calendar event type of 'open', since mod_assign only has the 'due' event type. No point in creating an override event
for an event type that does not exist.
=== 3.2 === === 3.2 ===
* External function mod_assign_external::get_assignments now returns additional optional fields: * External function mod_assign_external::get_assignments now returns additional optional fields:
- preventsubmissionnotingroup: Prevent submission not in group. - preventsubmissionnotingroup: Prevent submission not in group.

View file

@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics). $plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
$plugin->version = 2016120500; // The current module version (Date: YYYYMMDDXX). $plugin->version = 2017021500; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2016112900; // Requires this Moodle version. $plugin->requires = 2016112900; // Requires this Moodle version.
$plugin->cron = 60; $plugin->cron = 60;

View file

@ -123,10 +123,9 @@ function lesson_update_events($lesson, $override = null) {
} }
$oldevents = $DB->get_records('event', $conds); $oldevents = $DB->get_records('event', $conds);
// Now make a todo list of all that needs to be updated. // Now make a to-do list of all that needs to be updated.
if (empty($override)) { if (empty($override)) {
// We are updating the primary settings for the lesson, so we // We are updating the primary settings for the lesson, so we need to add all the overrides.
// need to add all the overrides.
$overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id)); $overrides = $DB->get_records('lesson_overrides', array('lessonid' => $lesson->id));
// As well as the original lesson (empty override). // As well as the original lesson (empty override).
$overrides[] = new stdClass(); $overrides[] = new stdClass();
@ -135,6 +134,9 @@ function lesson_update_events($lesson, $override = null) {
$overrides = array($override); $overrides = array($override);
} }
// Get group override priorities.
$grouppriorities = lesson_get_group_override_priorities($lesson->id);
foreach ($overrides as $current) { foreach ($overrides as $current) {
$groupid = isset($current->groupid) ? $current->groupid : 0; $groupid = isset($current->groupid) ? $current->groupid : 0;
$userid = isset($current->userid) ? $current->userid : 0; $userid = isset($current->userid) ? $current->userid : 0;
@ -164,8 +166,9 @@ function lesson_update_events($lesson, $override = null) {
$event->visible = instance_is_visible('lesson', $lesson); $event->visible = instance_is_visible('lesson', $lesson);
$event->eventtype = 'open'; $event->eventtype = 'open';
// Determine the event name. // Determine the event name and priority.
if ($groupid) { if ($groupid) {
// Group override event.
$params = new stdClass(); $params = new stdClass();
$params->lesson = $lesson->name; $params->lesson = $lesson->name;
$params->group = groups_get_group_name($groupid); $params->group = groups_get_group_name($groupid);
@ -174,48 +177,54 @@ function lesson_update_events($lesson, $override = null) {
continue; continue;
} }
$eventname = get_string('overridegroupeventname', 'lesson', $params); $eventname = get_string('overridegroupeventname', 'lesson', $params);
// Set group override priority.
if ($grouppriorities !== null) {
$openpriorities = $grouppriorities['open'];
if (isset($openpriorities[$available])) {
$event->priority = $openpriorities[$available];
}
}
} else if ($userid) { } else if ($userid) {
// User override event.
$params = new stdClass(); $params = new stdClass();
$params->lesson = $lesson->name; $params->lesson = $lesson->name;
$eventname = get_string('overrideusereventname', 'lesson', $params); $eventname = get_string('overrideusereventname', 'lesson', $params);
// Set user override priority.
$event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
} else { } else {
// The parent event.
$eventname = $lesson->name; $eventname = $lesson->name;
} }
if ($addopen or $addclose) { if ($addopen or $addclose) {
if ($deadline and $available and $event->timeduration <= LESSON_MAX_EVENT_LENGTH) { // Separate start and end events.
// Single event for the whole lesson. $event->timeduration = 0;
if ($available && $addopen) {
if ($oldevent = array_shift($oldevents)) { if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id; $event->id = $oldevent->id;
} else { } else {
unset($event->id); unset($event->id);
} }
$event->name = $eventname; $event->name = $eventname.' ('.get_string('lessonopens', 'lesson').')';
// The method calendar_event::create will reuse a db record if the id field is set. // The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event); calendar_event::create($event);
} else { }
// Separate start and end events. if ($deadline && $addclose) {
$event->timeduration = 0; if ($oldevent = array_shift($oldevents)) {
if ($available && $addopen) { $event->id = $oldevent->id;
if ($oldevent = array_shift($oldevents)) { } else {
$event->id = $oldevent->id; unset($event->id);
} else {
unset($event->id);
}
$event->name = $eventname.' ('.get_string('lessonopens', 'lesson').')';
// The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event);
} }
if ($deadline && $addclose) { $event->name = $eventname.' ('.get_string('lessoncloses', 'lesson').')';
if ($oldevent = array_shift($oldevents)) { $event->timestart = $deadline;
$event->id = $oldevent->id; $event->eventtype = 'close';
} else { if ($groupid && $grouppriorities !== null) {
unset($event->id); $closepriorities = $grouppriorities['close'];
if (isset($closepriorities[$deadline])) {
$event->priority = $closepriorities[$deadline];
} }
$event->name = $eventname.' ('.get_string('lessoncloses', 'lesson').')';
$event->timestart = $deadline;
$event->eventtype = 'close';
calendar_event::create($event);
} }
calendar_event::create($event);
} }
} }
} }
@ -227,6 +236,58 @@ function lesson_update_events($lesson, $override = null) {
} }
} }
/**
* Calculates the priorities of timeopen and timeclose values for group overrides for a lesson.
*
* @param int $lessonid The quiz ID.
* @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
*/
function lesson_get_group_override_priorities($lessonid) {
global $DB;
// Fetch group overrides.
$where = 'lessonid = :lessonid AND groupid IS NOT NULL';
$params = ['lessonid' => $lessonid];
$overrides = $DB->get_records_select('lesson_overrides', $where, $params, '', 'id, groupid, available, deadline');
if (!$overrides) {
return null;
}
$grouptimeopen = [];
$grouptimeclose = [];
foreach ($overrides as $override) {
if ($override->available !== null && !in_array($override->available, $grouptimeopen)) {
$grouptimeopen[] = $override->available;
}
if ($override->deadline !== null && !in_array($override->deadline, $grouptimeclose)) {
$grouptimeclose[] = $override->deadline;
}
}
// Sort open times in descending manner. The earlier open time gets higher priority.
rsort($grouptimeopen);
// Set priorities.
$opengrouppriorities = [];
$openpriority = 1;
foreach ($grouptimeopen as $timeopen) {
$opengrouppriorities[$timeopen] = $openpriority++;
}
// Sort close times in ascending manner. The later close time gets higher priority.
sort($grouptimeclose);
// Set priorities.
$closegrouppriorities = [];
$closepriority = 1;
foreach ($grouptimeclose as $timeclose) {
$closegrouppriorities[$timeclose] = $closepriority++;
}
return [
'open' => $opengrouppriorities,
'close' => $closegrouppriorities
];
}
/** /**
* This standard function will check all instances of this module * This standard function will check all instances of this module
* and make sure there are up-to-date events created for each of them. * and make sure there are up-to-date events created for each of them.

View file

@ -196,7 +196,14 @@ if ($mform->is_cancelled()) {
$event->trigger(); $event->trigger();
} }
lesson_update_events($lesson, $fromform); if ($groupmode) {
// Priorities may have shifted, so we need to update all of the calendar events for group overrides.
lesson_update_events($lesson);
} else {
// User override. We only need to update the calendar event for this user override.
lesson_update_events($lesson, $fromform);
}
if (!empty($fromform->submitbutton)) { if (!empty($fromform->submitbutton)) {
redirect($overridelisturl); redirect($overridelisturl);

View file

@ -0,0 +1,85 @@
<?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 mod/lesson/lib.php.
*
* @package mod_lesson
* @category test
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lesson/lib.php');
/**
* Unit tests for mod/lesson/lib.php.
*
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
class mod_lesson_lib_testcase extends advanced_testcase {
/**
* Test for lesson_get_group_override_priorities().
*/
public function test_lesson_get_group_override_priorities() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$dg = $this->getDataGenerator();
$course = $dg->create_course();
$lessonmodule = $this->getDataGenerator()->create_module('lesson', array('course' => $course->id));
$this->assertNull(lesson_get_group_override_priorities($lessonmodule->id));
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$now = 100;
$override1 = (object)[
'lessonid' => $lessonmodule->id,
'groupid' => $group1->id,
'available' => $now,
'deadline' => $now + 20
];
$DB->insert_record('lesson_overrides', $override1);
$override2 = (object)[
'lessonid' => $lessonmodule->id,
'groupid' => $group2->id,
'available' => $now - 10,
'deadline' => $now + 10
];
$DB->insert_record('lesson_overrides', $override2);
$priorities = lesson_get_group_override_priorities($lessonmodule->id);
$this->assertNotEmpty($priorities);
$openpriorities = $priorities['open'];
// Override 2's time open has higher priority since it is sooner than override 1's.
$this->assertEquals(1, $openpriorities[$override1->available]);
$this->assertEquals(2, $openpriorities[$override2->available]);
$closepriorities = $priorities['close'];
// Override 1's time close has higher priority since it is later than override 2's.
$this->assertEquals(2, $closepriorities[$override1->deadline]);
$this->assertEquals(1, $closepriorities[$override2->deadline]);
}
}

View file

@ -1202,10 +1202,9 @@ function quiz_update_events($quiz, $override = null) {
} }
$oldevents = $DB->get_records('event', $conds); $oldevents = $DB->get_records('event', $conds);
// Now make a todo list of all that needs to be updated. // Now make a to-do list of all that needs to be updated.
if (empty($override)) { if (empty($override)) {
// We are updating the primary settings for the quiz, so we // We are updating the primary settings for the lesson, so we need to add all the overrides.
// need to add all the overrides.
$overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id)); $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id));
// As well as the original quiz (empty override). // As well as the original quiz (empty override).
$overrides[] = new stdClass(); $overrides[] = new stdClass();
@ -1214,6 +1213,9 @@ function quiz_update_events($quiz, $override = null) {
$overrides = array($override); $overrides = array($override);
} }
// Get group override priorities.
$grouppriorities = quiz_get_group_override_priorities($quiz->id);
foreach ($overrides as $current) { foreach ($overrides as $current) {
$groupid = isset($current->groupid)? $current->groupid : 0; $groupid = isset($current->groupid)? $current->groupid : 0;
$userid = isset($current->userid)? $current->userid : 0; $userid = isset($current->userid)? $current->userid : 0;
@ -1243,8 +1245,9 @@ function quiz_update_events($quiz, $override = null) {
$event->visible = instance_is_visible('quiz', $quiz); $event->visible = instance_is_visible('quiz', $quiz);
$event->eventtype = 'open'; $event->eventtype = 'open';
// Determine the event name. // Determine the event name and priority.
if ($groupid) { if ($groupid) {
// Group override event.
$params = new stdClass(); $params = new stdClass();
$params->quiz = $quiz->name; $params->quiz = $quiz->name;
$params->group = groups_get_group_name($groupid); $params->group = groups_get_group_name($groupid);
@ -1253,48 +1256,54 @@ function quiz_update_events($quiz, $override = null) {
continue; continue;
} }
$eventname = get_string('overridegroupeventname', 'quiz', $params); $eventname = get_string('overridegroupeventname', 'quiz', $params);
// Set group override priority.
if ($grouppriorities !== null) {
$openpriorities = $grouppriorities['open'];
if (isset($openpriorities[$timeopen])) {
$event->priority = $openpriorities[$timeopen];
}
}
} else if ($userid) { } else if ($userid) {
// User override event.
$params = new stdClass(); $params = new stdClass();
$params->quiz = $quiz->name; $params->quiz = $quiz->name;
$eventname = get_string('overrideusereventname', 'quiz', $params); $eventname = get_string('overrideusereventname', 'quiz', $params);
// Set user override priority.
$event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
} else { } else {
// The parent event.
$eventname = $quiz->name; $eventname = $quiz->name;
} }
if ($addopen or $addclose) { if ($addopen or $addclose) {
if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { // Separate start and end events.
// Single event for the whole quiz. $event->timeduration = 0;
if ($timeopen && $addopen) {
if ($oldevent = array_shift($oldevents)) { if ($oldevent = array_shift($oldevents)) {
$event->id = $oldevent->id; $event->id = $oldevent->id;
} else { } else {
unset($event->id); unset($event->id);
} }
$event->name = $eventname; $event->name = $eventname.' ('.get_string('quizopens', 'quiz').')';
// The method calendar_event::create will reuse a db record if the id field is set. // The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event); calendar_event::create($event);
} else { }
// Separate start and end events. if ($timeclose && $addclose) {
$event->timeduration = 0; if ($oldevent = array_shift($oldevents)) {
if ($timeopen && $addopen) { $event->id = $oldevent->id;
if ($oldevent = array_shift($oldevents)) { } else {
$event->id = $oldevent->id; unset($event->id);
} else {
unset($event->id);
}
$event->name = $eventname.' ('.get_string('quizopens', 'quiz').')';
// The method calendar_event::create will reuse a db record if the id field is set.
calendar_event::create($event);
} }
if ($timeclose && $addclose) { $event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')';
if ($oldevent = array_shift($oldevents)) { $event->timestart = $timeclose;
$event->id = $oldevent->id; $event->eventtype = 'close';
} else { if ($groupid && $grouppriorities !== null) {
unset($event->id); $closepriorities = $grouppriorities['close'];
if (isset($closepriorities[$timeclose])) {
$event->priority = $closepriorities[$timeclose];
} }
$event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')';
$event->timestart = $timeclose;
$event->eventtype = 'close';
calendar_event::create($event);
} }
calendar_event::create($event);
} }
} }
} }
@ -1306,6 +1315,58 @@ function quiz_update_events($quiz, $override = null) {
} }
} }
/**
* Calculates the priorities of timeopen and timeclose values for group overrides for a quiz.
*
* @param int $quizid The quiz ID.
* @return array|null Array of group override priorities for open and close times. Null if there are no group overrides.
*/
function quiz_get_group_override_priorities($quizid) {
global $DB;
// Fetch group overrides.
$where = 'quiz = :quiz AND groupid IS NOT NULL';
$params = ['quiz' => $quizid];
$overrides = $DB->get_records_select('quiz_overrides', $where, $params, '', 'id, timeopen, timeclose');
if (!$overrides) {
return null;
}
$grouptimeopen = [];
$grouptimeclose = [];
foreach ($overrides as $override) {
if ($override->timeopen !== null && !in_array($override->timeopen, $grouptimeopen)) {
$grouptimeopen[] = $override->timeopen;
}
if ($override->timeclose !== null && !in_array($override->timeclose, $grouptimeclose)) {
$grouptimeclose[] = $override->timeclose;
}
}
// Sort open times in descending manner. The earlier open time gets higher priority.
rsort($grouptimeopen);
// Set priorities.
$opengrouppriorities = [];
$openpriority = 1;
foreach ($grouptimeopen as $timeopen) {
$opengrouppriorities[$timeopen] = $openpriority++;
}
// Sort close times in ascending manner. The later close time gets higher priority.
sort($grouptimeclose);
// Set priorities.
$closegrouppriorities = [];
$closepriority = 1;
foreach ($grouptimeclose as $timeclose) {
$closegrouppriorities[$timeclose] = $closepriority++;
}
return [
'open' => $opengrouppriorities,
'close' => $closegrouppriorities
];
}
/** /**
* List the actions that correspond to a view of this module. * List the actions that correspond to a view of this module.
* This is used by the participation report. * This is used by the participation report.

View file

@ -198,7 +198,13 @@ if ($mform->is_cancelled()) {
} }
quiz_update_open_attempts(array('quizid'=>$quiz->id)); quiz_update_open_attempts(array('quizid'=>$quiz->id));
quiz_update_events($quiz, $fromform); if ($groupmode) {
// Priorities may have shifted, so we need to update all of the calendar events for group overrides.
quiz_update_events($quiz);
} else {
// User override. We only need to update the calendar event for this user override.
quiz_update_events($quiz, $fromform);
}
if (!empty($fromform->submitbutton)) { if (!empty($fromform->submitbutton)) {
redirect($overridelisturl); redirect($overridelisturl);

View file

@ -449,4 +449,52 @@ class mod_quiz_lib_testcase extends advanced_testcase {
$this->assertEquals($quiz2->id, $attempt->quiz); $this->assertEquals($quiz2->id, $attempt->quiz);
} }
/**
* Test for quiz_get_group_override_priorities().
*/
public function test_quiz_get_group_override_priorities() {
global $DB;
$this->resetAfterTest();
$dg = $this->getDataGenerator();
$quizgen = $dg->get_plugin_generator('mod_quiz');
$course = $dg->create_course();
$quiz = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]);
$this->assertNull(quiz_get_group_override_priorities($quiz->id));
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
$now = 100;
$override1 = (object)[
'quiz' => $quiz->id,
'groupid' => $group1->id,
'timeopen' => $now,
'timeclose' => $now + 20
];
$DB->insert_record('quiz_overrides', $override1);
$override2 = (object)[
'quiz' => $quiz->id,
'groupid' => $group2->id,
'timeopen' => $now - 10,
'timeclose' => $now + 10
];
$DB->insert_record('quiz_overrides', $override2);
$priorities = quiz_get_group_override_priorities($quiz->id);
$this->assertNotEmpty($priorities);
$openpriorities = $priorities['open'];
// Override 2's time open has higher priority since it is sooner than override 1's.
$this->assertEquals(1, $openpriorities[$override1->timeopen]);
$this->assertEquals(2, $openpriorities[$override2->timeopen]);
$closepriorities = $priorities['close'];
// Override 1's time close has higher priority since it is later than override 2's.
$this->assertEquals(2, $closepriorities[$override1->timeclose]);
$this->assertEquals(1, $closepriorities[$override2->timeclose]);
}
} }

View file

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$version = 2017030600.00; // YYYYMMDD = weekly release date of this DEV branch. $version = 2017030700.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches. // RR = release increments - 00 in DEV branches.
// .XX = incremental changes. // .XX = incremental changes.