From 98a52c80bb7d9a10a5840b98be20418efbaccda8 Mon Sep 17 00:00:00 2001 From: Victor Deniz Date: Mon, 5 Nov 2018 11:41:55 +0000 Subject: [PATCH] MDL-63062 block_recentlyaccessedcourses: add web service --- course/amd/build/repository.min.js | Bin 364 -> 613 bytes course/amd/src/repository.js | 40 ++++++++- .../external/course_summary_exporter.php | 4 + course/externallib.php | 81 +++++++++++++++++ course/lib.php | 66 ++++++++++++++ course/tests/externallib_test.php | 84 +++++++++++++++++- lib/db/services.php | 8 ++ version.php | 2 +- 8 files changed, 282 insertions(+), 3 deletions(-) diff --git a/course/amd/build/repository.min.js b/course/amd/build/repository.min.js index 59fa5904e6bb30912d9f1e40a06f651a49132c62..a4020383178d7f9d7b3d4511e60999d3e47a72dc 100644 GIT binary patch delta 79 zcmaFE^ps_T2_utE%0xpcuF~SvqRbSl#9Hf#4I&c@EBK01lT-6b;*;}Bi;7c=CyOwK e$m*o0miQzVmpCRTrxq8drZ_|7tWs)gHLU?{1RS{l delta 16 XcmaFL@`h=H3FBlDCKHz0T1{&JF+l|U diff --git a/course/amd/src/repository.js b/course/amd/src/repository.js index a0c7a40f297..98b67d5a24d 100644 --- a/course/amd/src/repository.js +++ b/course/amd/src/repository.js @@ -57,7 +57,45 @@ define(['jquery', 'core/ajax'], function($, Ajax) { return Ajax.call([request])[0]; }; + /** + * Get the list of courses that the user has most recently accessed. + * + * @method getLastAccessedCourses + * @param {int} userid User from which the courses will be obtained + * @param {int} limit Only return this many results + * @param {int} offset Skip this many results from the start of the result set + * @param {string} sort Column to sort by and direction, e.g. 'shortname asc' + * @return {promise} Resolved with an array of courses + */ + var getLastAccessedCourses = function(userid, limit, offset, sort) { + var args = {}; + + if (typeof userid !== 'undefined') { + args.limit = limit; + } + + if (typeof limit !== 'undefined') { + args.limit = limit; + } + + if (typeof offset !== 'undefined') { + args.offset = offset; + } + + if (typeof sort !== 'undefined') { + args.sort = sort; + } + + var request = { + methodname: 'core_course_get_recent_courses', + args: args + }; + + return Ajax.call([request])[0]; + }; + return { - getEnrolledCoursesByTimelineClassification: getEnrolledCoursesByTimelineClassification + getEnrolledCoursesByTimelineClassification: getEnrolledCoursesByTimelineClassification, + getLastAccessedCourses: getLastAccessedCourses }; }); diff --git a/course/classes/external/course_summary_exporter.php b/course/classes/external/course_summary_exporter.php index 7b13d65a47c..2a1416c36cf 100644 --- a/course/classes/external/course_summary_exporter.php +++ b/course/classes/external/course_summary_exporter.php @@ -141,6 +141,10 @@ class course_summary_exporter extends \core\external\exporter { ), 'hidden' => array( 'type' => PARAM_BOOL + ), + 'timeaccess' => array( + 'type' => PARAM_INT, + 'optional' => true ) ); } diff --git a/course/externallib.php b/course/externallib.php index e8b1e43953a..3d350422fd0 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die; use core_course\external\course_summary_exporter; require_once("$CFG->libdir/externallib.php"); +require_once("lib.php"); /** * Course external functions @@ -3886,4 +3887,84 @@ class core_course_external extends external_api { ) ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + * @since Moodle 3.6 + */ + public static function get_recent_courses_parameters() { + return new external_function_parameters( + array( + 'userid' => new external_value(PARAM_INT, 'id of the user, default to current user', VALUE_DEFAULT, 0), + 'limit' => new external_value(PARAM_INT, 'result set limit', VALUE_DEFAULT, 0), + 'offset' => new external_value(PARAM_INT, 'Result set offset', VALUE_DEFAULT, 0), + 'sort' => new external_value(PARAM_TEXT, 'Sort string', VALUE_DEFAULT, null) + ) + ); + } + + /** + * Get last accessed courses adding additional course information like images. + * + * @param int $userid User id from which the courses will be obtained + * @param int $limit Restrict result set to this amount + * @param int $offset Skip this number of records from the start of the result set + * @param string|null $sort SQL string for sorting + * @return array List of courses + * @throws invalid_parameter_exception + */ + public static function get_recent_courses(int $userid = 0, int $limit = 0, int $offset = 0, string $sort = null) { + global $USER, $PAGE; + + if (empty($userid)) { + $userid = $USER->id; + } + + $params = self::validate_parameters(self::get_recent_courses_parameters(), + array( + 'userid' => $userid, + 'limit' => $limit, + 'offset' => $offset, + 'sort' => $sort + ) + ); + + $userid = $params['userid']; + $limit = $params['limit']; + $offset = $params['offset']; + $sort = $params['sort']; + + $usercontext = context_user::instance($userid); + + self::validate_context($usercontext); + + if ($userid != $USER->id and !has_capability('moodle/user:viewdetails', $usercontext)) { + return array(); + } + + $courses = course_get_recent_courses($userid, $limit, $offset, $sort); + + $renderer = $PAGE->get_renderer('core'); + + $recentcourses = array_map(function($course) use ($renderer) { + context_helper::preload_from_record($course); + $context = context_course::instance($course->id); + $exporter = new course_summary_exporter($course, ['context' => $context]); + return $exporter->export($renderer); + }, $courses); + + return $recentcourses; + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.6 + */ + public static function get_recent_courses_returns() { + return new external_multiple_structure(course_summary_exporter::get_read_structure(), 'Courses'); + } } diff --git a/course/lib.php b/course/lib.php index d7b9245c99e..f28f29a9264 100644 --- a/course/lib.php +++ b/course/lib.php @@ -4529,3 +4529,69 @@ function get_hidden_courses_on_timeline($user = null) { return $ids; } + +/** + * Returns a list of the most recently courses accessed by a user + * + * @param int $userid User id from which the courses will be obtained + * @param int $limit Restrict result set to this amount + * @param int $offset Skip this number of records from the start of the result set + * @param string|null $sort SQL string for sorting + * @return array + */ +function course_get_recent_courses(int $userid = null, int $limit = 0, int $offset = 0, string $sort = null) { + + global $CFG, $USER, $DB; + + if (empty($userid)) { + $userid = $USER->id; + } + + $basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category', + 'shortname', 'fullname', 'userid', 'timeaccess'); + + $sort = trim($sort); + if (empty($sort)) { + $sort = 'timeaccess DESC'; + } else { + $rawsorts = explode(',', $sort); + $sorts = array(); + foreach ($rawsorts as $rawsort) { + $rawsort = trim($rawsort); + $sorts[] = trim($rawsort); + } + $sort = implode(',', $sorts); + } + + $orderby = "ORDER BY $sort"; + + $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); + + $coursefields = 'c.' .join(',', $basefields); + + $sql = "SELECT $ctxfields, $coursefields + FROM {course} c + JOIN {context} ctx ON ctx.contextlevel = :contextlevel + AND ctx.instanceid = c.id + JOIN {user_lastaccess} ul ON ul.courseid = c.id + WHERE ul.userid = :userid + $orderby"; + $params = ['userid' => $userid, 'contextlevel' => CONTEXT_COURSE]; + + $recentcourses = $DB->get_records_sql($sql, $params, $offset, $limit); + + // Filter courses if last access field is hidden. + $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields)); + + if ($userid != $USER->id && isset($hiddenfields['lastaccess'])) { + $recentcourses = array_filter($recentcourses, function($course) { + context_helper::preload_from_record($course); + $context = context_course::instance($course->id, IGNORE_MISSING); + // If last access was a hidden field, a user requesting info about another user would need permission to view hidden + // fields. + return has_capability('moodle/course:viewhiddenuserfields', $context); + }); + } + + return $recentcourses; +} diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index 71a4e0727ad..39c187f46eb 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -2744,4 +2744,86 @@ class core_course_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals($expectedcourses, $actual); $this->assertEquals($expectednextoffset, $result['nextoffset']); } -} + + /** + * Test the get_recent_courses function. + */ + public function test_get_recent_courses() { + global $USER, $DB; + + $this->resetAfterTest(); + $generator = $this->getDataGenerator(); + + set_config('hiddenuserfields', 'lastaccess'); + + $courses = array(); + for ($i = 1; $i < 12; $i++) { + $courses[] = $generator->create_course(); + }; + + $student = $generator->create_user(); + $teacher = $generator->create_user(); + + foreach ($courses as $course) { + $generator->enrol_user($student->id, $course->id, 'student'); + } + + $generator->enrol_user($teacher->id, $courses[0]->id, 'teacher'); + + $this->setUser($student); + + $result = core_course_external::get_recent_courses($USER->id); + + // No course accessed. + $this->assertCount(0, $result); + + foreach ($courses as $course) { + core_course_external::view_course($course->id); + } + + // Every course accessed. + $result = core_course_external::get_recent_courses($USER->id); + $this->assertCount( 11, $result); + + // Every course accessed, result limited to 10 courses. + $result = core_course_external::get_recent_courses($USER->id, 10); + $this->assertCount(10, $result); + + $guestcourse = $generator->create_course( + (object)array('shortname' => 'guestcourse', + 'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED, + 'enrol_guest_password_0' => '')); + core_course_external::view_course($guestcourse->id); + + // Every course accessed, even the not enrolled one. + $result = core_course_external::get_recent_courses($USER->id); + $this->assertCount(12, $result); + + // Offset 5, return 7 out of 12. + $result = core_course_external::get_recent_courses($USER->id, 0, 5); + $this->assertCount(7, $result); + + // Offset 5 and limit 3, return 3 out of 12. + $result = core_course_external::get_recent_courses($USER->id, 3, 5); + $this->assertCount(3, $result); + + // Sorted by course id ASC. + $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id ASC'); + $this->assertEquals($courses[0]->id, array_shift($result)->id); + + // Sorted by course id DESC. + $result = core_course_external::get_recent_courses($USER->id, 0, 0, 'id DESC'); + $this->assertEquals($guestcourse->id, array_shift($result)->id); + + // If last access is hidden, only get the courses where has viewhiddenuserfields capability. + $this->setUser($teacher); + $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher')); + $usercontext = context_user::instance($student->id); + $this->assignUserCapability('moodle/user:viewdetails', $usercontext, $teacherroleid); + + // Sorted by course id DESC. + $result = core_course_external::get_recent_courses($student->id); + $this->assertCount(1, $result); + $this->assertEquals($courses[0]->id, array_shift($result)->id); + } +} \ No newline at end of file diff --git a/lib/db/services.php b/lib/db/services.php index 4f41b1ca291..4b76fff9a14 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -544,6 +544,14 @@ $functions = array( 'type' => 'read', 'ajax' => true ), + 'core_course_get_recent_courses' => array( + 'classname' => 'core_course_external', + 'methodname' => 'get_recent_courses', + 'classpath' => 'course/externallib.php', + 'description' => 'List of courses a user has accessed most recently.', + 'type' => 'read', + 'ajax' => true + ), 'core_enrol_get_course_enrolment_methods' => array( 'classname' => 'core_enrol_external', 'methodname' => 'get_course_enrolment_methods', diff --git a/version.php b/version.php index bdfeda5c41a..894b3ba2f59 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2018110300.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2018110300.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. -- 2.43.0