diff --git a/course/lib.php b/course/lib.php index 18bcfe9212c..7268824761a 100644 --- a/course/lib.php +++ b/course/lib.php @@ -2612,31 +2612,68 @@ function update_course($data, $editoroptions = NULL) { } /** - * Average number of participants - * @return integer + * Calculate the average number of enrolled participants per course. + * + * This is intended for statistics purposes during the site registration. Only visible courses are taken into account. + * Front page enrolments are excluded. + * + * @param bool $onlyactive Consider only active enrolments in enabled plugins and obey the enrolment time restrictions. + * @param int $lastloginsince If specified, count only users who logged in after this timestamp. + * @return float */ -function average_number_of_participants() { - global $DB, $SITE; +function average_number_of_participants(bool $onlyactive = false, int $lastloginsince = null): float { + global $DB; + + $params = [ + 'siteid' => SITEID, + ]; + + $sql = "SELECT DISTINCT ue.userid, e.courseid + FROM {user_enrolments} ue + JOIN {enrol} e ON e.id = ue.enrolid + JOIN {course} c ON c.id = e.courseid "; + + if ($onlyactive || $lastloginsince) { + $sql .= "JOIN {user} u ON u.id = ue.userid "; + } + + $sql .= "WHERE e.courseid <> :siteid + AND c.visible = 1 "; + + if ($onlyactive) { + $sql .= "AND ue.status = :active + AND e.status = :enabled + AND ue.timestart < :now1 + AND (ue.timeend = 0 OR ue.timeend > :now2) "; + + // Same as in the enrollib - the rounding should help caching in the database. + $now = round(time(), -2); + + $params += [ + 'active' => ENROL_USER_ACTIVE, + 'enabled' => ENROL_INSTANCE_ENABLED, + 'now1' => $now, + 'now2' => $now, + ]; + } + + if ($lastloginsince) { + $sql .= "AND u.lastlogin > :lastlogin "; + $params['lastlogin'] = $lastloginsince; + } + + $sql = "SELECT COUNT(*) + FROM ($sql) total"; - //count total of enrolments for visible course (except front page) - $sql = 'SELECT COUNT(*) FROM ( - SELECT DISTINCT ue.userid, e.courseid - FROM {user_enrolments} ue, {enrol} e, {course} c - WHERE ue.enrolid = e.id - AND e.courseid <> :siteid - AND c.id = e.courseid - AND c.visible = 1) total'; - $params = array('siteid' => $SITE->id); $enrolmenttotal = $DB->count_records_sql($sql, $params); + // Get the number of visible courses (exclude the front page). + $coursetotal = $DB->count_records('course', ['visible' => 1]); + $coursetotal = $coursetotal - 1; - //count total of visible courses (minus front page) - $coursetotal = $DB->count_records('course', array('visible' => 1)); - $coursetotal = $coursetotal - 1 ; - - //average of enrolment if (empty($coursetotal)) { $participantaverage = 0; + } else { $participantaverage = $enrolmenttotal / $coursetotal; } diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index 2fcc9bee1b8..fef3933a62f 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -6945,4 +6945,78 @@ class core_course_courselib_testcase extends advanced_testcase { // Manager has permissions. $this->assertTrue(course_allowed_module($course, 'assign', $manager)); } + + /** + * Test the {@link average_number_of_participants()} function. + */ + public function test_average_number_of_participants() { + global $DB; + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + $now = time(); + + // If there are no courses, expect zero number of participants per course. + $this->assertEquals(0, average_number_of_participants()); + + $c1 = $generator->create_course(); + $c2 = $generator->create_course(); + + // If there are no users, expect zero number of participants per course. + $this->assertEquals(0, average_number_of_participants()); + + $t1 = $generator->create_user(['lastlogin' => $now]); + $s1 = $generator->create_user(['lastlogin' => $now]); + $s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]); + $s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]); + $s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]); + + // We have courses, we have users, but no enrolments yet. + $this->assertEquals(0, average_number_of_participants()); + + // Front page enrolments are ignored. + $generator->enrol_user($t1->id, SITEID, 'teacher'); + $this->assertEquals(0, average_number_of_participants()); + + // The teacher enrolled into one of the two courses. + $generator->enrol_user($t1->id, $c1->id, 'editingteacher'); + $this->assertEquals(0.5, average_number_of_participants()); + + // The teacher enrolled into both courses. + $generator->enrol_user($t1->id, $c2->id, 'editingteacher'); + $this->assertEquals(1, average_number_of_participants()); + + // Student 1 enrolled in the Course 1 only. + $generator->enrol_user($s1->id, $c1->id, 'student'); + $this->assertEquals(1.5, average_number_of_participants()); + + // Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future). + $generator->enrol_user($s2->id, $c1->id, 'student'); + $generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS); + $this->assertEquals(2.5, average_number_of_participants()); + $this->assertEquals(2, average_number_of_participants(true)); + + // Student 3 enrolled in the Course 1, but the enrolment already expired. + $generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS); + $this->assertEquals(3, average_number_of_participants()); + $this->assertEquals(2, average_number_of_participants(true)); + + // Student 4 enrolled in both courses, but the enrolment has been suspended. + $generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED); + $generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED); + $this->assertEquals(4, average_number_of_participants()); + $this->assertEquals(2, average_number_of_participants(true)); + + // Consider only t1 and s1 who logged in recently. + $this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS)); + + // Consider only t1, s1, s2 and s3 who logged in in recent weeks. + $this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS)); + + // Hidden courses are excluded from stats. + $DB->set_field('course', 'visible', 0, ['id' => $c1->id]); + $this->assertEquals(3, average_number_of_participants()); + $this->assertEquals(1, average_number_of_participants(true)); + } + }