From 00a66396c71b7241e04371bf70254b95c73047dc Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Fri, 5 Jul 2019 10:05:33 +0800 Subject: [PATCH] MDL-66144 course: add function to calculate user course dates --- course/lib.php | 151 ++++ course/tests/courselib_test.php | 1305 +++++++++++++++++++++++++++++++ lang/en/cache.php | 1 + lib/db/caches.php | 8 + lib/enrollib.php | 2 +- 5 files changed, 1466 insertions(+), 1 deletion(-) diff --git a/course/lib.php b/course/lib.php index 52432a46bb3..1550a12719d 100644 --- a/course/lib.php +++ b/course/lib.php @@ -4666,3 +4666,154 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs return $recentcourses; } + +/** + * Calculate the course start date and offset for the given user ids. + * + * If the course is a fixed date course then the course start date will be returned. + * If the course is a relative date course then the course date will be calculated and + * and offset provided. + * + * The dates are returned as an array with the index being the user id. The array + * contains the start date and start offset values for the user. + * + * If the user is not enrolled in the course then the course start date will be returned. + * + * If we have a course which starts on 1563244000 and 2 users, id 123 and 456, where the + * former is enrolled in the course at 1563244693 and the latter is not enrolled then the + * return value would look like: + * [ + * '123' => [ + * 'start' => 1563244693, + * 'startoffset' => 693 + * ], + * '456' => [ + * 'start' => 1563244000, + * 'startoffset' => 0 + * ] + * ] + * + * @param stdClass $course The course to fetch dates for. + * @param array $userids The list of user ids to get dates for. + * @return array + */ +function course_get_course_dates_for_user_ids(stdClass $course, array $userids): array { + if (empty($course->relativedatesmode)) { + // This course isn't set to relative dates so we can early return with the course + // start date. + return array_reduce($userids, function($carry, $userid) use ($course) { + $carry[$userid] = [ + 'start' => $course->startdate, + 'startoffset' => 0 + ]; + return $carry; + }, []); + } + + // We're dealing with a relative dates course now so we need to calculate some dates. + $cache = cache::make('core', 'course_user_dates'); + $dates = []; + $uncacheduserids = []; + + // Try fetching the values from the cache so that we don't need to do a DB request. + foreach ($userids as $userid) { + $cachekey = "{$course->id}_{$userid}"; + $cachedvalue = $cache->get($cachekey); + + if ($cachedvalue === false) { + // Looks like we haven't seen this user for this course before so we'll have + // to fetch it. + $uncacheduserids[] = $userid; + } else { + [$start, $startoffset] = $cachedvalue; + $dates[$userid] = [ + 'start' => $start, + 'startoffset' => $startoffset + ]; + } + } + + if (!empty($uncacheduserids)) { + // Load the enrolments for any users we haven't seen yet. Set the "onlyactive" param + // to false because it filters out users with enrolment start times in the future which + // we don't want. + $enrolments = enrol_get_course_users($course->id, false, $uncacheduserids); + + foreach ($uncacheduserids as $userid) { + // Find the user enrolment that has the earliest start date. + $enrolment = array_reduce(array_values($enrolments), function($carry, $enrolment) use ($userid) { + // Only consider enrolments for this user if the user enrolment is active and the + // enrolment method is enabled. + if ( + $enrolment->uestatus == ENROL_USER_ACTIVE && + $enrolment->estatus == ENROL_INSTANCE_ENABLED && + $enrolment->id == $userid + ) { + if (is_null($carry)) { + // Haven't found an enrolment yet for this user so use the one we just found. + $carry = $enrolment; + } else { + // We've already found an enrolment for this user so let's use which ever one + // has the earliest start time. + $carry = $carry->uetimestart < $enrolment->uetimestart ? $carry : $enrolment; + } + } + + return $carry; + }, null); + + if ($enrolment) { + // The course is in relative dates mode so we calculate the student's start + // date based on their enrolment start date. + $start = $course->startdate > $enrolment->uetimestart ? $course->startdate : $enrolment->uetimestart; + $startoffset = $start - $course->startdate; + } else { + // The user is not enrolled in the course so default back to the course start date. + $start = $course->startdate; + $startoffset = 0; + } + + $dates[$userid] = [ + 'start' => $start, + 'startoffset' => $startoffset + ]; + + $cachekey = "{$course->id}_{$userid}"; + $cache->set($cachekey, [$start, $startoffset]); + } + } + + return $dates; +} + +/** + * Calculate the course start date and offset for the given user id. + * + * If the course is a fixed date course then the course start date will be returned. + * If the course is a relative date course then the course date will be calculated and + * and offset provided. + * + * The return array contains the start date and start offset values for the user. + * + * If the user is not enrolled in the course then the course start date will be returned. + * + * If we have a course which starts on 1563244000. If a user's enrolment starts on 1563244693 + * then the return would be: + * [ + * 'start' => 1563244693, + * 'startoffset' => 693 + * ] + * + * If the use was not enrolled then the return would be: + * [ + * 'start' => 1563244000, + * 'startoffset' => 0 + * ] + * + * @param stdClass $course The course to fetch dates for. + * @param int $userid The user id to get dates for. + * @return array + */ +function course_get_course_dates_for_user_id(stdClass $course, int $userid): array { + return (course_get_course_dates_for_user_ids($course, [$userid]))[$userid]; +} diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index fde58cc05ed..79429f64ac6 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -5149,4 +5149,1309 @@ class core_course_courselib_testcase extends advanced_testcase { $this->assertCount(3, $result); $this->assertArrayNotHasKey($courses[0]->id, $result); } + + /** + * Test cases for the course_get_course_dates_for_user_ids tests. + */ + public function get_course_get_course_dates_for_user_ids_test_cases() { + $now = time(); + $pastcoursestart = $now - 100; + $futurecoursestart = $now + 100; + + return [ + 'future course start fixed no users enrolled' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [[], []], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 1 users enrolled future' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 1 users enrolled past' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled future' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled past' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled mixed' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled 2 methods' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled 2 methods 1 disabled' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled 2 methods 2 disabled' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_DISABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ + 'relativedatemode' => false, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative no users enrolled' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [[], []], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 1 users enrolled future' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 1 users enrolled past' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 2 users enrolled future' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $futurecoursestart + 20, + 'startoffset' => 20 + ] + ] + ], + 'future course start relative 2 users enrolled past' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 2 users enrolled mixed' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 2 users enrolled 2 methods' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'future course start relative 2 users enrolled 2 methods 1 disabled' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 20, + 'startoffset' => 20 + ], + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'future course start relative 2 users enrolled 2 methods 2 disabled' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_DISABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + 'future course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart + 20, + 'startoffset' => 20 + ], + [ + 'start' => $futurecoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'future course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ + 'relativedatemode' => true, + 'coursestart' => $futurecoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED] + ], + // User 2. + [ + 'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED] + ] + ], + 'expected' => [ + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $futurecoursestart, + 'startoffset' => 0 + ] + ] + ], + + // Course start date in the past. + 'past course start fixed no users enrolled' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [[], []], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 1 users enrolled future' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 1 users enrolled past' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled future' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled past' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled mixed' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled 2 methods' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled 2 methods 1 disabled' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled 2 methods 2 disabled' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_DISABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ + 'relativedatemode' => false, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative no users enrolled' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [[], []], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 1 users enrolled future' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 1 users enrolled past' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + [] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 2 users enrolled future' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $pastcoursestart + 20, + 'startoffset' => 20 + ] + ] + ], + 'past course start relative 2 users enrolled past' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 2 users enrolled mixed' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]], + // User 2. + ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 2 users enrolled 2 methods' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ], + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'past course start relative 2 users enrolled 2 methods 1 disabled' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 20, + 'startoffset' => 20 + ], + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'past course start relative 2 users enrolled 2 methods 2 disabled' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_DISABLED], + ['self', ENROL_INSTANCE_DISABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ], + 'past course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart + 20, + 'startoffset' => 20 + ], + [ + 'start' => $pastcoursestart + 10, + 'startoffset' => 10 + ] + ] + ], + 'past course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [ + 'relativedatemode' => true, + 'coursestart' => $pastcoursestart, + 'usercount' => 2, + 'enrolmentmethods' => [ + ['manual', ENROL_INSTANCE_ENABLED], + ['self', ENROL_INSTANCE_ENABLED] + ], + 'enrolled' => [ + // User 1. + [ + 'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED] + ], + // User 2. + [ + 'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED], + 'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED] + ] + ], + 'expected' => [ + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ], + [ + 'start' => $pastcoursestart, + 'startoffset' => 0 + ] + ] + ] + ]; + } + + /** + * Test the course_get_course_dates_for_user_ids function. + * + * @dataProvider get_course_get_course_dates_for_user_ids_test_cases() + * @param bool $relativedatemode Set the course to relative dates mode + * @param int $coursestart Course start date + * @param int $usercount Number of users to create + * @param array $enrolmentmethods Enrolment methods to set for the course + * @param array $enrolled Enrolment config for to set for the users + * @param array $expected Expected output + */ + public function test_course_get_course_dates_for_user_ids( + $relativedatemode, + $coursestart, + $usercount, + $enrolmentmethods, + $enrolled, + $expected + ) { + global $DB; + $this->resetAfterTest(); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course(['startdate' => $coursestart]); + $course->relativedatesmode = $relativedatemode; + $users = []; + + for ($i = 0; $i < $usercount; $i++) { + $users[] = $generator->create_user(); + } + + foreach ($enrolmentmethods as [$type, $status]) { + $record = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => $type]); + $plugin = enrol_get_plugin($type); + if ($record->status != $status) { + $plugin->update_status($record, $status); + } + } + + foreach ($enrolled as $index => $enrolconfig) { + $user = $users[$index]; + foreach ($enrolconfig as $type => [$starttime, $status]) { + $generator->enrol_user($user->id, $course->id, 'student', $type, $starttime, 0, $status); + } + } + + $userids = array_map(function($user) { + return $user->id; + }, $users); + $actual = course_get_course_dates_for_user_ids($course, $userids); + + foreach ($expected as $index => $exp) { + $userid = $userids[$index]; + $act = $actual[$userid]; + + $this->assertEquals($exp['start'], $act['start']); + $this->assertEquals($exp['startoffset'], $act['startoffset']); + } + } + + /** + * Test that calling course_get_course_dates_for_user_ids multiple times in the + * same request fill fetch the correct data for the user. + */ + public function test_course_get_course_dates_for_user_ids_multiple_calls() { + $this->resetAfterTest(); + + $generator = $this->getDataGenerator(); + $now = time(); + $coursestart = $now - 1000; + $course = $generator->create_course(['startdate' => $coursestart]); + $course->relativedatesmode = true; + $user1 = $generator->create_user(); + $user2 = $generator->create_user(); + $user1start = $coursestart + 100; + $user2start = $coursestart + 200; + + $generator->enrol_user($user1->id, $course->id, 'student', 'manual', $user1start); + $generator->enrol_user($user2->id, $course->id, 'student', 'manual', $user2start); + + $result = course_get_course_dates_for_user_ids($course, [$user1->id]); + $this->assertEquals($user1start, $result[$user1->id]['start']); + + $result = course_get_course_dates_for_user_ids($course, [$user1->id, $user2->id]); + $this->assertEquals($user1start, $result[$user1->id]['start']); + $this->assertEquals($user2start, $result[$user2->id]['start']); + + $result = course_get_course_dates_for_user_ids($course, [$user2->id]); + $this->assertEquals($user2start, $result[$user2->id]['start']); + } } diff --git a/lang/en/cache.php b/lang/en/cache.php index ab5658426db..2a4ee80feb2 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -46,6 +46,7 @@ $string['cachedef_coursecattree'] = 'Course categories tree'; $string['cachedef_coursecompletion'] = 'Course completion status'; $string['cachedef_coursecontacts'] = 'List of course contacts'; $string['cachedef_coursemodinfo'] = 'Accumulated information about modules and sections for each course'; +$string['cachedef_course_user_dates'] = 'The user dates for courses set to relative dates mode'; $string['cachedef_completion'] = 'Activity completion status'; $string['cachedef_databasemeta'] = 'Database meta information'; $string['cachedef_eventinvalidation'] = 'Event invalidation'; diff --git a/lib/db/caches.php b/lib/db/caches.php index 7247f54386a..ba9412af15f 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -400,4 +400,12 @@ $definitions = array( 'simplekeys' => true, 'staticacceleration' => true ], + + // Cache the user dates for courses set to relative dates mode. + 'course_user_dates' => [ + 'mode' => cache_store::MODE_REQUEST, + 'simplekeys' => true, + 'simpledata' => true, + 'staticacceleration' => true + ], ); diff --git a/lib/enrollib.php b/lib/enrollib.php index 5b668941c47..69ec99f45d4 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -1664,7 +1664,7 @@ function enrol_get_course_users($courseid = false, $onlyactive = false, $usersfi $sql = "SELECT ue.id AS ueid, ue.status AS uestatus, ue.enrolid AS ueenrolid, ue.timestart AS uetimestart, ue.timeend AS uetimeend, ue.modifierid AS uemodifierid, ue.timecreated AS uetimecreated, - ue.timemodified AS uetimemodified, + ue.timemodified AS uetimemodified, e.status AS estatus, u.* FROM {user_enrolments} ue JOIN {enrol} e ON e.id = ue.enrolid JOIN {user} u ON ue.userid = u.id