Merge branch 'MDL-71235-master' of https://github.com/dcai/moodle

This commit is contained in:
Adrian Greeve 2021-04-29 14:42:35 +08:00
commit 98edee3c4a
12 changed files with 455 additions and 119 deletions

View File

@ -40,6 +40,9 @@ class cm_completion_details {
/** @var completion_info The completion info instance for this cm's course. */
protected $completioninfo = null;
/** @var object The completion data. */
protected $completiondata = null;
/** @var cm_info The course module information. */
protected $cminfo = null;
@ -62,6 +65,7 @@ class cm_completion_details {
*/
public function __construct(completion_info $completioninfo, cm_info $cminfo, int $userid, bool $returndetails = true) {
$this->completioninfo = $completioninfo;
$this->completiondata = $completioninfo->get_data($cminfo, false, $userid);
$this->cminfo = $cminfo;
$this->userid = $userid;
$this->returndetails = $returndetails;
@ -87,7 +91,7 @@ class cm_completion_details {
return [];
}
$completiondata = $this->completioninfo->get_data($this->cminfo, false, $this->userid);
$completiondata = $this->completiondata;
$hasoverride = !empty($this->overridden_by());
$details = [];
@ -155,8 +159,7 @@ class cm_completion_details {
* @return int The overall completion state for this course module.
*/
public function get_overall_completion(): int {
$completiondata = $this->completioninfo->get_data($this->cminfo, false, $this->userid);
return (int)$completiondata->completionstate;
return (int)$this->completiondata->completionstate;
}
/**
@ -183,8 +186,7 @@ class cm_completion_details {
* @return int|null
*/
public function overridden_by(): ?int {
$completiondata = $this->completioninfo->get_data($this->cminfo);
return isset($completiondata->overrideby) ? (int)$completiondata->overrideby : null;
return isset($this->completiondata->overrideby) ? (int)$this->completiondata->overrideby : null;
}
/**
@ -219,6 +221,15 @@ class cm_completion_details {
return false;
}
/**
* Completion state timemodified
*
* @return int timestamp
*/
public function get_timemodified(): int {
return (int)$this->completiondata->timemodified;
}
/**
* Generates an instance of this class.
*
@ -229,7 +240,7 @@ class cm_completion_details {
*/
public static function get_instance(cm_info $cminfo, int $userid, bool $returndetails = true): cm_completion_details {
$course = $cminfo->get_course();
$completioninfo = new completion_info($course);
$completioninfo = new \completion_info($course);
return new self($completioninfo, $cminfo, $userid, $returndetails);
}
}

View File

@ -221,7 +221,7 @@ class core_completion_external extends external_api {
* @throws moodle_exception
*/
public static function get_activities_completion_status($courseid, $userid) {
global $CFG, $USER;
global $CFG, $USER, $PAGE;
require_once($CFG->libdir . '/grouplib.php');
$warnings = array();
@ -253,25 +253,24 @@ class core_completion_external extends external_api {
$results = array();
foreach ($activities as $activity) {
// Check if current user has visibility on this activity.
if (!$activity->uservisible) {
continue;
}
// Get progress information and state (we must use get_data because it works for all user roles in course).
$activitycompletiondata = $completion->get_data($activity, true, $user->id);
$results[] = array(
'cmid' => $activity->id,
'modname' => $activity->modname,
'instance' => $activity->instance,
'state' => $activitycompletiondata->completionstate,
'timecompleted' => $activitycompletiondata->timemodified,
'tracking' => $activity->completion,
'overrideby' => $activitycompletiondata->overrideby,
'valueused' => core_availability\info::completion_value_used($course, $activity->id)
$exporter = new \core_completion\external\completion_info_exporter(
$course,
$activity,
$userid,
);
$renderer = $PAGE->get_renderer('core');
$data = (array)$exporter->export($renderer);
$results[] = array_merge([
'cmid' => $activity->id,
'modname' => $activity->modname,
'instance' => $activity->instance,
'tracking' => $activity->completion,
], $data);
}
$results = array(
@ -292,21 +291,59 @@ class core_completion_external extends external_api {
array(
'statuses' => new external_multiple_structure(
new external_single_structure(
array(
'cmid' => new external_value(PARAM_INT, 'comment ID'),
[
'cmid' => new external_value(PARAM_INT, 'course module ID'),
'modname' => new external_value(PARAM_PLUGIN, 'activity module name'),
'instance' => new external_value(PARAM_INT, 'instance ID'),
'state' => new external_value(PARAM_INT, 'completion state value:
0 means incomplete, 1 complete,
2 complete pass, 3 complete fail'),
'timecompleted' => new external_value(PARAM_INT, 'timestamp for completed activity'),
'tracking' => new external_value(PARAM_INT, 'type of tracking:
0 means none, 1 manual, 2 automatic'),
'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the status, or null',
'state' => new external_value(PARAM_INT,
"Completion state value:
0 means incomplete,
1 complete,
2 complete pass,
3 complete fail"
),
'timecompleted' => new external_value(PARAM_INT,
'timestamp for completed activity'),
'tracking' => new external_value(PARAM_INT,
"type of tracking:
0 means none,
1 manual,
2 automatic"
),
'overrideby' => new external_value(PARAM_INT,
'The user id who has overriden the status, or null', VALUE_OPTIONAL),
'valueused' => new external_value(PARAM_BOOL,
'Whether the completion status affects the availability of another activity.',
VALUE_OPTIONAL),
'valueused' => new external_value(PARAM_BOOL, 'Whether the completion status affects the availability
of another activity.', VALUE_OPTIONAL),
), 'Activity'
'hascompletion' => new external_value(PARAM_BOOL,
'Whether this activity module has completion enabled',
VALUE_OPTIONAL),
'isautomatic' => new external_value(PARAM_BOOL,
'Whether this activity module instance tracks completion automatically.',
VALUE_OPTIONAL),
'istrackeduser' => new external_value(PARAM_BOOL,
'Whether completion is being tracked for this user.',
VALUE_OPTIONAL),
'uservisible' => new external_value(PARAM_BOOL,
'Whether this activity is visible to the user.',
VALUE_OPTIONAL),
'details' => new external_multiple_structure(
new external_single_structure(
[
'rulename' => new external_value(PARAM_TEXT, 'Rule name'),
'rulevalue' => new external_single_structure(
[
'status' => new external_value(PARAM_INT, 'Completion status'),
'description' => new external_value(PARAM_TEXT, 'Completion description'),
]
)
]
),
VALUE_DEFAULT,
[]
),
], 'Activity'
), 'List of activities status'
),
'warnings' => new external_warnings()

View File

@ -0,0 +1,153 @@
<?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/>.
declare(strict_types=1);
namespace core_completion\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
/**
* Completion info exporter
*
* @package core_completion
* @copyright 2021 Dongsheng Cai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_info_exporter extends \core\external\exporter {
/**
* @var object $course moodle course object
*/
private $course;
/**
* @var object|cm_info $cm course module info
*/
private $cminfo;
/**
* @var int $userid user id
*/
private $userid;
/**
* Constructor for the completion info exporter.
*
* @param object $course course object
* @param object|cm_info $cm course module info
* @param int $userid user id
* @param array $related related values
*/
public function __construct(object $course, object $cm, int $userid, array $related = []) {
$this->course = $course;
$this->cminfo = \cm_info::create($cm);
$this->userid = $userid;
parent::__construct([], $related);
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output): array {
$cmcompletion = \core_completion\cm_completion_details::get_instance($this->cminfo, $this->userid);
$cmcompletiondetails = $cmcompletion->get_details();
$details = [];
foreach ($cmcompletiondetails as $rulename => $rulevalue) {
$details[] = [
'rulename' => $rulename,
'rulevalue' => (array)$rulevalue,
];
}
return [
'state' => $cmcompletion->get_overall_completion(),
'timecompleted' => $cmcompletion->get_timemodified(),
'overrideby' => $cmcompletion->overridden_by(),
'valueused' => \core_availability\info::completion_value_used($this->course, $this->cminfo->id),
'hascompletion' => $cmcompletion->has_completion(),
'isautomatic' => $cmcompletion->is_automatic(),
'istrackeduser' => $cmcompletion->is_tracked_user(),
'overallstatus' => $cmcompletion->get_overall_completion(),
'uservisible' => $this->cminfo->uservisible,
'details' => $details,
];
}
/**
* Return the list of additional properties used only for display.
*
* @return array Keys are the property names, and value their definition.
*/
public static function define_other_properties(): array {
return [
'state' => [
'type' => PARAM_INT,
'description' => 'overall completion state of this course module.',
],
'timecompleted' => [
'type' => PARAM_INT,
'description' => 'course completion timestamp.',
],
'overrideby' => [
'type' => PARAM_INT,
'description' => 'user ID that has overridden the completion state of this activity for the user.',
'null' => NULL_ALLOWED,
],
'valueused' => [
'type' => PARAM_BOOL,
'description' => 'True if module is used in a condition, false otherwise.',
],
'hascompletion' => [
'type' => PARAM_BOOL,
'description' => 'Whether this activity module has completion enabled.'
],
'isautomatic' => [
'type' => PARAM_BOOL,
'description' => 'Whether this activity module instance tracks completion automatically.'
],
'istrackeduser' => [
'type' => PARAM_BOOL,
'description' => 'Checks whether completion is being tracked for this user.'
],
'uservisible' => [
'type' => PARAM_BOOL,
'description' => 'Whether this activity is visible to user.'
],
'details' => [
'multiple' => true,
'description' => 'An array of completion details containing the description and status.',
'type' => [
'rulename' => [
'type' => PARAM_TEXT,
],
'rulevalue' => [
'type' => [
'status' => [
'type' => PARAM_INT,
],
'description' => [
'type' => PARAM_TEXT,
]
]
]
]
],
];
}
}

View File

@ -54,7 +54,8 @@ class cm_completion_details_test extends advanced_testcase {
* @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
* @return cm_completion_details
*/
protected function setup_data(?int $completion, array $completionoptions = []): cm_completion_details {
protected function setup_data(?int $completion, array $completionoptions = [],
object $mockcompletiondata = null): cm_completion_details {
if (is_null($completion)) {
$completion = COMPLETION_TRACKING_AUTOMATIC;
}
@ -69,6 +70,12 @@ class cm_completion_details_test extends advanced_testcase {
->method('is_enabled')
->willReturn($completion);
if (!empty($mockcompletiondata)) {
$this->completioninfo->expects($this->any())
->method('get_data')
->willReturn($mockcompletiondata);
}
// Build a mock cm_info instance.
$mockcminfo = $this->getMockBuilder(cm_info::class)
->disableOriginalConstructor()
@ -171,12 +178,8 @@ class cm_completion_details_test extends advanced_testcase {
* @param int $state
*/
public function test_get_overall_completion(int $state) {
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC);
$this->completioninfo->expects($this->once())
->method('get_data')
->willReturn((object)['completionstate' => $state]);
$completiondata = (object)['completionstate' => $state];
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, [], $completiondata);
$this->assertEquals($state, $cmcompletion->get_overall_completion());
}
@ -274,12 +277,7 @@ class cm_completion_details_test extends advanced_testcase {
$options['completionusegrade'] = true;
}
$cmcompletion = $this->setup_data($completion, $options);
$this->completioninfo->expects($this->any())
->method('get_data')
->willReturn($getdatareturn);
$cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
$this->assertEquals($expecteddetails, $cmcompletion->get_details());
}
}

View File

@ -92,7 +92,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
* Test update_activity_completion_status
*/
public function test_get_activities_completion_status() {
global $DB, $CFG;
global $DB, $CFG, $PAGE;
$this->resetAfterTest(true);
@ -105,17 +105,29 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
'groupmodeforce' => 1));
availability_completion\condition::wipe_static_cache();
$data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
array('completion' => 1));
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
array('completion' => 1));
$data = $this->getDataGenerator()->create_module('data',
['course' => $course->id],
['completion' => COMPLETION_TRACKING_MANUAL],
);
$forum = $this->getDataGenerator()->create_module('forum',
['course' => $course->id],
['completion' => COMPLETION_TRACKING_MANUAL],
);
$forumautocompletion = $this->getDataGenerator()->create_module('forum',
['course' => $course->id],
['showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC],
);
$availability = '{"op":"&","c":[{"type":"completion","cm":' . $forum->cmid .',"e":1}],"showc":[true]}';
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['availability' => $availability]);
$assign = $this->getDataGenerator()->create_module('assign',
['course' => $course->id],
['availability' => $availability],
);
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
array('completion' => 1, 'visible' => 0));
$cmdata = get_coursemodule_from_id('data', $data->cmid);
$cmforum = get_coursemodule_from_id('forum', $forum->cmid);
$cmforumautocompletion = get_coursemodule_from_id('forum', $forumautocompletion->cmid);
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
@ -139,8 +151,13 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 4 activities, but only 3 with completion enabled and one of those is hidden.
$this->assertCount(2, $result['statuses']);
// We added 5 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 5;
$numberofhidden = 1;
$numberofcompletions = $numberofactivities - $numberofhidden;
$numberofstatusstudent = 3;
$this->assertCount($numberofstatusstudent, $result['statuses']);
$activitiesfound = 0;
foreach ($result['statuses'] as $status) {
@ -149,14 +166,41 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertEquals(COMPLETION_COMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertTrue($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertFalse($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(0, $details);
} else if ($status['cmid'] == $forumautocompletion->cmid) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertTrue($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(1, $details);
$this->assertEquals('completionview', $details[0]['rulename']);
$this->assertEquals(0, $details[0]['rulevalue']['status']);
} else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertFalse($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(0, $details);
}
}
$this->assertEquals(2, $activitiesfound);
$this->assertEquals(3, $activitiesfound);
// Teacher should see students status, they are in different groups but the teacher can access all groups.
$this->setUser($teacher);
@ -165,8 +209,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 4 activities, but only 3 with completion enabled and one of those is hidden.
$this->assertCount(3, $result['statuses']);
$this->assertCount($numberofcompletions, $result['statuses']);
// Override status by teacher.
$completion->update_state($cmforum, COMPLETION_INCOMPLETE, $student->id, true);
@ -197,8 +240,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 4 activities, but only 3 with completion enabled (one of those is hidden but the teacher can see it).
$this->assertCount(3, $result['statuses']);
$this->assertCount($numberofcompletions, $result['statuses']);
$activitiesfound = 0;
foreach ($result['statuses'] as $status) {
@ -206,13 +248,17 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$activitiesfound++;
$this->assertEquals(COMPLETION_COMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
} else if ($status['cmid'] == $forumautocompletion->cmid) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
} else {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
}
}
$this->assertEquals(3, $activitiesfound);
$this->assertEquals(4, $activitiesfound);
// Change teacher role capabilities (disable access all groups).
$context = context_course::instance($course->id);
@ -232,8 +278,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 4 activities, but only 3 with completion enabled and one of those is hidden.
$this->assertCount(3, $result['statuses']);
$this->assertCount($numberofcompletions, $result['statuses']);
}
/**

View File

@ -108,7 +108,15 @@ class course_summary_exporter extends \core\external\exporter {
),
'visible' => array(
'type' => PARAM_BOOL,
)
),
'showactivitydates' => [
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED
],
'showcompletionconditions' => [
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED
],
);
}

View File

@ -27,9 +27,11 @@
defined('MOODLE_INTERNAL') || die;
use core_course\external\course_summary_exporter;
use core_availability\info;
require_once("$CFG->libdir/externallib.php");
require_once("lib.php");
require_once(__DIR__ . "/lib.php");
/**
* Course external functions
@ -84,7 +86,7 @@ class core_course_external extends external_api {
* @since Moodle 2.2
*/
public static function get_course_contents($courseid, $options = array()) {
global $CFG, $DB;
global $CFG, $DB, $USER, $PAGE;
require_once($CFG->dirroot . "/course/lib.php");
require_once($CFG->libdir . '/completionlib.php');
@ -221,6 +223,8 @@ class core_course_external extends external_api {
if (empty($filters['excludemodules']) and !empty($modinfosections[$section->section])) {
foreach ($modinfosections[$section->section] as $cmid) {
$cm = $modinfo->cms[$cmid];
$cminfo = cm_info::create($cm);
$activitydates = \core\activity_dates::get_dates_for_module($cminfo, $USER->id);
// Stop here if the module is not visible to the user on the course main page:
// The user can't access the module and the user can't view the module on the course page.
@ -272,17 +276,15 @@ class core_course_external extends external_api {
$module['customdata'] = json_encode($cm->customdata);
$module['completion'] = $cm->completion;
$module['noviewlink'] = plugin_supports('mod', $cm->modname, FEATURE_NO_VIEW_LINK, false);
$module['dates'] = $activitydates;
// Check module completion.
$completion = $completioninfo->is_enabled($cm);
if ($completion != COMPLETION_DISABLED) {
$completiondata = $completioninfo->get_data($cm, true);
$module['completiondata'] = array(
'state' => $completiondata->completionstate,
'timecompleted' => $completiondata->timemodified,
'overrideby' => $completiondata->overrideby,
'valueused' => core_availability\info::completion_value_used($course, $cm->id)
);
$exporter = new \core_completion\external\completion_info_exporter($course, $cm, $USER->id);
$renderer = $PAGE->get_renderer('core');
$modulecompletiondata = (array)$exporter->export($renderer);
$module['completiondata'] = $modulecompletiondata;
}
if (!empty($cm->showdescription) or $module['noviewlink']) {
@ -430,6 +432,8 @@ class core_course_external extends external_api {
* @since Moodle 2.2
*/
public static function get_course_contents_returns() {
$completiondefinition = \core_completion\external\completion_info_exporter::get_read_structure(VALUE_DEFAULT, []);
return new external_multiple_structure(
new external_single_structure(
array(
@ -472,16 +476,16 @@ class core_course_external extends external_api {
VALUE_OPTIONAL),
'completion' => new external_value(PARAM_INT, 'Type of completion tracking:
0 means none, 1 manual, 2 automatic.', VALUE_OPTIONAL),
'completiondata' => new external_single_structure(
array(
'state' => new external_value(PARAM_INT, 'Completion state value:
0 means incomplete, 1 complete, 2 complete pass, 3 complete fail'),
'timecompleted' => new external_value(PARAM_INT, 'Timestamp for completion status.'),
'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the
status.'),
'valueused' => new external_value(PARAM_BOOL, 'Whether the completion status affects
the availability of another activity.', VALUE_OPTIONAL),
), 'Module completion data.', VALUE_OPTIONAL
'completiondata' => $completiondefinition,
'dates' => new external_multiple_structure(
new external_single_structure(
array(
'label' => new external_value(PARAM_TEXT, 'date label'),
'timestamp' => new external_value(PARAM_INT, 'date timestamp'),
)
),
VALUE_DEFAULT,
[]
),
'contents' => new external_multiple_structure(
new external_single_structure(
@ -606,6 +610,8 @@ class core_course_external extends external_api {
$courseinfo['format'] = $course->format;
$courseinfo['startdate'] = $course->startdate;
$courseinfo['enddate'] = $course->enddate;
$courseinfo['showactivitydates'] = $course->showactivitydates;
$courseinfo['showcompletionconditions'] = $course->showcompletionconditions;
if (array_key_exists('numsections', $courseformatoptions)) {
// For backward-compartibility
$courseinfo['numsections'] = $courseformatoptions['numsections'];
@ -737,6 +743,9 @@ class core_course_external extends external_api {
'value' => new external_value(PARAM_RAW, 'course format option value')
)), 'additional options for particular course format', VALUE_OPTIONAL
),
'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
'showcompletionconditions' => new external_value(PARAM_BOOL,
'Whether the activity completion conditions are shown or not'),
'customfields' => new external_multiple_structure(
new external_single_structure(
['name' => new external_value(PARAM_RAW, 'The name of the custom field'),
@ -2490,6 +2499,8 @@ class core_course_external extends external_api {
$coursereturns['contacts'] = $coursecontacts;
$coursereturns['enrollmentmethods'] = $enroltypes;
$coursereturns['sortorder'] = $course->sortorder;
$coursereturns['showactivitydates'] = $course->showactivitydates;
$coursereturns['showcompletionconditions'] = $course->showcompletionconditions;
$handler = core_course\customfield\course_handler::create();
if ($customfields = $handler->export_instance_data($course->id)) {
@ -2629,6 +2640,9 @@ class core_course_external extends external_api {
'summaryformat' => new external_format_value('summary'),
'summaryfiles' => new external_files('summary files in the summary field', VALUE_OPTIONAL),
'overviewfiles' => new external_files('additional overview files attached to this course'),
'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
'showcompletionconditions' => new external_value(PARAM_BOOL,
'Whether the activity completion conditions are shown or not'),
'contacts' => new external_multiple_structure(
new external_single_structure(
array(

View File

@ -4736,8 +4736,11 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
$userid = $USER->id;
}
$basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
'shortname', 'fullname', 'timeaccess', 'component', 'visible');
$basefields = [
'id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
'shortname', 'fullname', 'timeaccess', 'component', 'visible',
'showactivitydates', 'showcompletionconditions',
];
$sort = trim($sort);
if (empty($sort)) {
@ -4756,7 +4759,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
$coursefields = 'c.' .join(',', $basefields);
$coursefields = 'c.' . join(',', $basefields);
// Ask the favourites service to give us the join SQL for favourited courses,
// so we can include favourite information in the query.

View File

@ -1080,9 +1080,14 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$DB->set_field('course_sections', 'visible', 0,
array('course' => $course->id, 'section' => 4));
$forumcompleteauto = $this->getDataGenerator()->create_module('forum',
array('course' => $course->id, 'intro' => 'forum completion tracking auto', 'trackingtype' => 2),
array('showdescription' => true, 'completionview' => 1, 'completion' => COMPLETION_TRACKING_AUTOMATIC));
$forumcompleteautocm = get_coursemodule_from_id('forum', $forumcompleteauto->cmid);
rebuild_course_cache($course->id, true);
return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm);
return array($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm);
}
/**
@ -1145,7 +1150,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$this->assertEquals(5, $testexecuted);
$this->assertEquals(0, $sections[0]['section']);
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
$this->assertCount(1, $sections[1]['modules']);
$this->assertCount(1, $sections[2]['modules']);
$this->assertCount(1, $sections[3]['modules']); // One module for the section with availability restrictions.
@ -1187,7 +1192,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(4, $sections); // Nothing for the not visible section.
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
$this->assertCount(1, $sections[1]['modules']);
$this->assertCount(1, $sections[2]['modules']);
$this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
@ -1208,7 +1213,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(5, $sections); // Include fake section with stealth activities.
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
$this->assertCount(1, $sections[1]['modules']);
$this->assertCount(1, $sections[2]['modules']);
$this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
@ -1273,7 +1278,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(1, $sections);
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
}
/**
@ -1333,7 +1338,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(4, $sections);
$this->assertCount(1, $sections[0]['modules']);
$this->assertCount(2, $sections[0]['modules']);
$this->assertEquals($forumcm->id, $sections[0]['modules'][0]["id"]);
}
@ -1361,13 +1366,14 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
}
/**
* Test get course contents completion
* Test get course contents completion manual
*/
public function test_get_course_contents_completion() {
public function test_get_course_contents_completion_manual() {
global $CFG;
$this->resetAfterTest(true);
list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm) = $this->prepare_get_course_contents_test();
list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) =
$this->prepare_get_course_contents_test();
availability_completion\condition::wipe_static_cache();
// Test activity not completed yet.
@ -1376,13 +1382,18 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
$completiondata = $result[0]['modules'][0]["completiondata"];
$this->assertCount(1, $result[0]['modules']);
$this->assertEquals("forum", $result[0]['modules'][0]["modname"]);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]);
$this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']);
$this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']);
$this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
$this->assertFalse($result[0]['modules'][0]["completiondata"]['valueused']);
$this->assertEquals(0, $completiondata['state']);
$this->assertEquals(0, $completiondata['timecompleted']);
$this->assertEmpty($completiondata['overrideby']);
$this->assertFalse($completiondata['valueused']);
$this->assertTrue($completiondata['hascompletion']);
$this->assertFalse($completiondata['isautomatic']);
$this->assertFalse($completiondata['istrackeduser']);
$this->assertTrue($completiondata['uservisible']);
// Set activity completed.
core_completion_external::update_activity_completion_status_manually($forumcm->id, true);
@ -1402,13 +1413,18 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
$completiondata = $result[0]['modules'][0]["completiondata"];
$this->assertCount(1, $result[0]['modules']);
$this->assertEquals("label", $result[0]['modules'][0]["modname"]);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $result[0]['modules'][0]["completion"]);
$this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['state']);
$this->assertEquals(0, $result[0]['modules'][0]["completiondata"]['timecompleted']);
$this->assertEmpty($result[0]['modules'][0]["completiondata"]['overrideby']);
$this->assertTrue($result[0]['modules'][0]["completiondata"]['valueused']);
$this->assertEquals(0, $completiondata['state']);
$this->assertEquals(0, $completiondata['timecompleted']);
$this->assertEmpty($completiondata['overrideby']);
$this->assertTrue($completiondata['valueused']);
$this->assertTrue($completiondata['hascompletion']);
$this->assertFalse($completiondata['isautomatic']);
$this->assertFalse($completiondata['istrackeduser']);
$this->assertTrue($completiondata['uservisible']);
// Disable completion.
$CFG->enablecompletion = 0;
@ -1420,6 +1436,47 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$this->assertArrayNotHasKey('completiondata', $result[0]['modules'][0]);
}
/**
* Test get course contents completion auto
*/
public function test_get_course_contents_completion_auto() {
global $CFG;
$this->resetAfterTest(true);
list($course, $forumcm, $datacm, $pagecm, $labelcm, $urlcm, $forumcompleteautocm) =
$this->prepare_get_course_contents_test();
availability_completion\condition::wipe_static_cache();
// Test activity not completed yet.
$result = core_course_external::get_course_contents($course->id, [
[
"name" => "modname",
"value" => "forum"
],
[
"name" => "modid",
"value" => $forumcompleteautocm->instance
]
]);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $result);
$forummod = $result[0]['modules'][0];
$completiondata = $forummod["completiondata"];
$this->assertCount(1, $result[0]['modules']);
$this->assertEquals("forum", $forummod["modname"]);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $forummod["completion"]);
$this->assertEquals(0, $completiondata['state']);
$this->assertEquals(0, $completiondata['timecompleted']);
$this->assertEmpty($completiondata['overrideby']);
$this->assertFalse($completiondata['valueused']);
$this->assertTrue($completiondata['hascompletion']);
$this->assertTrue($completiondata['isautomatic']);
$this->assertFalse($completiondata['istrackeduser']);
$this->assertTrue($completiondata['uservisible']);
$this->assertCount(1, $completiondata['details']);
}
/**
* Test mimetype is returned for resources with showtype set.
*/
@ -1542,7 +1599,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(5, $sections); // All the sections, including the "not visible" one.
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
$this->assertCount(1, $sections[1]['modules']);
$this->assertCount(1, $sections[2]['modules']);
$this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
@ -1564,7 +1621,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$sections = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $sections);
$this->assertCount(6, $sections); // Include fake section with stealth activities.
$this->assertCount(5, $sections[0]['modules']);
$this->assertCount(6, $sections[0]['modules']);
$this->assertCount(1, $sections[1]['modules']);
$this->assertCount(1, $sections[2]['modules']);
$this->assertCount(0, $sections[3]['modules']); // No modules for the section with availability restrictions.
@ -2576,16 +2633,16 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(3, $result['courses']);
// Expect to receive all the fields.
$this->assertCount(38, $result['courses'][0]);
$this->assertCount(39, $result['courses'][1]); // One more field because is not the site course.
$this->assertCount(39, $result['courses'][2]); // One more field because is not the site course.
$this->assertCount(40, $result['courses'][0]);
$this->assertCount(41, $result['courses'][1]); // One more field because is not the site course.
$this->assertCount(41, $result['courses'][2]); // One more field because is not the site course.
$result = core_course_external::get_courses_by_field('id', $course1->id);
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(1, $result['courses']);
$this->assertEquals($course1->id, $result['courses'][0]['id']);
// Expect to receive all the fields.
$this->assertCount(39, $result['courses'][0]);
$this->assertCount(41, $result['courses'][0]);
// Check default values for course format topics.
$this->assertCount(2, $result['courses'][0]['courseformatoptions']);
foreach ($result['courses'][0]['courseformatoptions'] as $option) {
@ -2646,15 +2703,15 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$result = core_course_external::get_courses_by_field();
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(2, $result['courses']);
$this->assertCount(31, $result['courses'][0]);
$this->assertCount(32, $result['courses'][1]); // One field more (course format options), not present in site course.
$this->assertCount(33, $result['courses'][0]);
$this->assertCount(34, $result['courses'][1]); // One field more (course format options), not present in site course.
$result = core_course_external::get_courses_by_field('id', $course1->id);
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(1, $result['courses']);
$this->assertEquals($course1->id, $result['courses'][0]['id']);
// Expect to receive all the files that a student can see.
$this->assertCount(32, $result['courses'][0]);
$this->assertCount(34, $result['courses'][0]);
// Check default filters.
$filters = $result['courses'][0]['filters'];
@ -2699,15 +2756,15 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$result = core_course_external::get_courses_by_field();
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(2, $result['courses']);
$this->assertCount(31, $result['courses'][0]); // Site course.
$this->assertCount(14, $result['courses'][1]); // Only public information, not enrolled.
$this->assertCount(33, $result['courses'][0]); // Site course.
$this->assertCount(16, $result['courses'][1]); // Only public information, not enrolled.
$result = core_course_external::get_courses_by_field('id', $course1->id);
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(1, $result['courses']);
$this->assertEquals($course1->id, $result['courses'][0]['id']);
// Expect to receive all the files that a authenticated can see.
$this->assertCount(14, $result['courses'][0]);
$this->assertCount(16, $result['courses'][0]);
// Course 2 is not visible.
$result = core_course_external::get_courses_by_field('id', $course2->id);

View File

@ -433,6 +433,8 @@ class core_enrol_external extends external_api {
'isfavourite' => isset($favouritecourseids[$course->id]),
'hidden' => $hidden,
'overviewfiles' => $overviewfiles,
'showactivitydates' => $course->showactivitydates,
'showcompletionconditions' => $course->showcompletionconditions,
];
if ($returnusercount) {
$courseresult['enrolledusercount'] = $enrolledusercount;
@ -479,6 +481,8 @@ class core_enrol_external extends external_api {
'isfavourite' => new external_value(PARAM_BOOL, 'If the user marked this course a favourite.', VALUE_OPTIONAL),
'hidden' => new external_value(PARAM_BOOL, 'If the user hide the course from the dashboard.', VALUE_OPTIONAL),
'overviewfiles' => new external_files('Overview files attached to this course.', VALUE_OPTIONAL),
'showactivitydates' => new external_value(PARAM_BOOL, 'Whether the activity dates are shown or not'),
'showcompletionconditions' => new external_value(PARAM_BOOL, 'Whether the activity completion conditions are shown or not'),
)
)
);

View File

@ -489,12 +489,15 @@ abstract class exporter {
/**
* Returns the read structure.
*
* @param int $required Whether is required.
* @param mixed $default The default value.
*
* @return external_single_structure
*/
final public static function get_read_structure() {
final public static function get_read_structure($required = VALUE_REQUIRED, $default = null) {
$properties = self::read_properties_definition();
return self::get_read_structure_from_properties($properties);
return self::get_read_structure_from_properties($properties, $required, $default);
}
/**

View File

@ -575,10 +575,13 @@ function enrol_get_my_courses($fields = null, $sort = null, $limit = 0, $coursei
return array();
}
$basefields = array('id', 'category', 'sortorder',
'shortname', 'fullname', 'idnumber',
'startdate', 'visible',
'groupmode', 'groupmodeforce', 'cacherev');
$basefields = [
'id', 'category', 'sortorder',
'shortname', 'fullname', 'idnumber',
'startdate', 'visible',
'groupmode', 'groupmodeforce', 'cacherev',
'showactivitydates', 'showcompletionconditions',
];
if (empty($fields)) {
$fields = $basefields;