MDL-63062 block_recentlyaccessedcourses: add web service
authorVictor Deniz <victor@moodle.com>
Mon, 5 Nov 2018 11:41:55 +0000 (11:41 +0000)
committerVíctor Déniz Falcón <victor@moodle.com>
Mon, 5 Nov 2018 23:46:43 +0000 (23:46 +0000)
course/amd/build/repository.min.js
course/amd/src/repository.js
course/classes/external/course_summary_exporter.php
course/externallib.php
course/lib.php
course/tests/externallib_test.php
lib/db/services.php
version.php

index 59fa590..a402038 100644 (file)
Binary files a/course/amd/build/repository.min.js and b/course/amd/build/repository.min.js differ
index a0c7a40..98b67d5 100644 (file)
@@ -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
     };
 });
index 7b13d65..2a1416c 100644 (file)
@@ -141,6 +141,10 @@ class course_summary_exporter extends \core\external\exporter {
             ),
             'hidden' => array(
                 'type' => PARAM_BOOL
+            ),
+            'timeaccess' => array(
+                'type' => PARAM_INT,
+                'optional' => true
             )
         );
     }
index e8b1e43..3d35042 100644 (file)
@@ -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');
+    }
 }
index d7b9245..f28f29a 100644 (file)
@@ -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;
+}
index 71a4e07..39c187f 100644 (file)
@@ -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
index 4f41b1c..4b76fff 100644 (file)
@@ -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',
index bdfeda5..894b3ba 100644 (file)
@@ -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.