MDL-71189 core_completion: Implementing custom completion sort ordering

This adds the requirement for activities supporting custom completion to
specify the order all completion conditions should be displayed for that
activity. It also implements the sorting that takes place.
This commit is contained in:
Michael Hawkins 2021-04-08 14:20:50 +08:00
parent c997fc7784
commit f105612d7f
4 changed files with 158 additions and 3 deletions

View file

@ -184,4 +184,11 @@ abstract class activity_custom_completion {
* @return array * @return array
*/ */
abstract public function get_custom_rule_descriptions(): array; abstract public function get_custom_rule_descriptions(): array;
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
abstract public function get_sort_order(): array;
} }

View file

@ -79,6 +79,7 @@ class cm_completion_details {
* Fetches the completion details for a user. * Fetches the completion details for a user.
* *
* @return array An array of completion details for a user containing the completion requirement's description and status. * @return array An array of completion details for a user containing the completion requirement's description and status.
* @throws \coding_exception
*/ */
public function get_details(): array { public function get_details(): array {
if (!$this->is_automatic()) { if (!$this->is_automatic()) {
@ -135,6 +136,8 @@ class cm_completion_details {
'description' => $this->cmcompletion->get_custom_rule_description($rule), 'description' => $this->cmcompletion->get_custom_rule_description($rule),
]; ];
} }
$details = $this->sort_completion_details($details);
} }
} else { } else {
if (function_exists($this->cminfo->modname . '_get_completion_state')) { if (function_exists($this->cminfo->modname . '_get_completion_state')) {
@ -149,10 +152,36 @@ class cm_completion_details {
} }
} }
return $details; return $details;
} }
/**
* Sort completion details in the order specified by the activity's custom completion implementation.
*
* @param array $details The completion details to be sorted.
* @return array
* @throws \coding_exception
*/
protected function sort_completion_details(array $details): array {
$sortorder = $this->cmcompletion->get_sort_order();
$sorteddetails = [];
foreach ($sortorder as $sortedkey) {
if (isset($details[$sortedkey])) {
$sorteddetails[$sortedkey] = $details[$sortedkey];
}
}
// Make sure the sorted list includes all of the conditions that were set.
if (count($sorteddetails) < count($details)) {
$exceptiontext = get_class($this->cmcompletion) .'::get_sort_order() is missing one or more completion conditions.' .
' All custom and standard conditions that apply to this activity must be listed.';
throw new \coding_exception($exceptiontext);
}
return $sorteddetails;
}
/** /**
* Fetches the overall completion state of this course module. * Fetches the overall completion state of this course module.
* *

View file

@ -52,10 +52,12 @@ class cm_completion_details_test extends advanced_testcase {
* *
* @param int|null $completion The completion tracking mode for the module. * @param int|null $completion The completion tracking mode for the module.
* @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.) * @param array $completionoptions Completion options (e.g. completionview, completionusegrade, etc.)
* @param object $mockcompletiondata Mock data to be returned by get_data.
* @param string $modname The modname to set in the cm if a specific one is required.
* @return cm_completion_details * @return cm_completion_details
*/ */
protected function setup_data(?int $completion, array $completionoptions = [], protected function setup_data(?int $completion, array $completionoptions = [],
object $mockcompletiondata = null): cm_completion_details { object $mockcompletiondata = null, $modname = 'somenonexistentmod'): cm_completion_details {
if (is_null($completion)) { if (is_null($completion)) {
$completion = COMPLETION_TRACKING_AUTOMATIC; $completion = COMPLETION_TRACKING_AUTOMATIC;
} }
@ -88,7 +90,7 @@ class cm_completion_details_test extends advanced_testcase {
->will($this->returnValueMap([ ->will($this->returnValueMap([
['completion', $completion], ['completion', $completion],
['instance', 1], ['instance', 1],
['modname', 'somenonexistentmod'], ['modname', $modname],
['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED], ['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED],
['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null], ['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null],
])); ]));
@ -280,4 +282,118 @@ class cm_completion_details_test extends advanced_testcase {
$cmcompletion = $this->setup_data($completion, $options, $getdatareturn); $cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
$this->assertEquals($expecteddetails, $cmcompletion->get_details()); $this->assertEquals($expecteddetails, $cmcompletion->get_details());
} }
/**
* Data provider for test_get_details().
* @return array[]
*/
public function get_details_custom_order_provider() {
return [
'Custom and view/grade standard conditions, view first and grade last' => [
true,
true,
[
'completionsubmit' => true,
],
'assign',
['completionview', 'completionsubmit', 'completionusegrade'],
],
'Custom and view/grade standard conditions, grade not last' => [
true,
true,
[
'completionminattempts' => 2,
'completionusegrade' => 50,
'completionpassorattemptsexhausted' => 1,
],
'quiz',
['completionview', 'completionminattempts', 'completionusegrade', 'completionpassorattemptsexhausted'],
],
'Custom and grade standard conditions only, no view condition' => [
false,
true,
[
'completionsubmit' => true,
],
'assign',
['completionsubmit', 'completionusegrade'],
],
'Custom and view standard conditions only, no grade condition' => [
true,
false,
[
'completionsubmit' => true
],
'assign',
['completionview', 'completionsubmit'],
],
'View and grade conditions only, activity with no custom conditions' => [
true,
true,
[
'completionview' => true,
'completionusegrade' => true
],
'workshop',
['completionview', 'completionusegrade'],
],
'View condition only, activity with no custom conditions' => [
true,
false,
[
'completionview' => true,
],
'workshop',
['completionview'],
],
];
}
/**
* Test custom sort order is functioning in \core_completion\cm_completion_details::get_details().
*
* @dataProvider get_details_custom_order_provider
* @param bool $completionview Completion status of the "view" completion condition.
* @param bool $completiongrade Completion status of the "must receive grade" completion condition.
* @param array $customcompletionrules Custom completion requirements, along with their values.
* @param string $modname The name of the module having data fetched.
* @param array $expectedorder The expected order of completion conditions returned about the module.
*/
public function test_get_details_custom_order(bool $completionview, bool $completiongrade, array $customcompletionrules,
string $modname, array $expectedorder) {
$options['customcompletion'] = [];
$customcompletiondata = [];
if ($completionview) {
$options['completionview'] = true;
}
if ($completiongrade) {
$options['completionusegrade'] = true;
}
// Set up the completion rules for the completion info.
foreach ($customcompletionrules as $customtype => $isenabled) {
$customcompletiondata[$customtype] = COMPLETION_COMPLETE;
}
$getdatareturn = (object)[
'viewed' => $completionview ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
'completiongrade' => $completiongrade ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE,
'customcompletion' => $customcompletiondata,
];
$cmcompletion = $this->setup_data(COMPLETION_TRACKING_AUTOMATIC, $options, $getdatareturn, $modname);
$this->completioninfo->expects($this->any())
->method('get_data')
->willReturn($getdatareturn);
$fetcheddetails = $cmcompletion->get_details();
// Check the expected number of items are returned, and sorted in the correct order.
$this->assertCount(count($expectedorder), $fetcheddetails);
$this->assertTrue((array_keys($fetcheddetails) === $expectedorder));
}
} }

View file

@ -109,6 +109,9 @@ information provided here is intended especially for developers.
- get_custom_rule_descriptions(): Returns an associative array with values containing the user-facing textual description - get_custom_rule_descriptions(): Returns an associative array with values containing the user-facing textual description
of the custom completion rules (which serve as the keys to these values). of the custom completion rules (which serve as the keys to these values).
e.g. ['completionsubmit' => 'Must submit'] e.g. ['completionsubmit' => 'Must submit']
- get_sort_order(): Returns an array listing the order the activity module's completion rules should be displayed to the user,
including both custom completion and relevant core completion rules
e.g. ['completionview', 'completionsubmit', 'completionusegrade']
* Admin setting admin_setting_configmulticheckbox now supports lazy-loading the options list by * Admin setting admin_setting_configmulticheckbox now supports lazy-loading the options list by
supplying a callback function instead of an array of options. supplying a callback function instead of an array of options.
* A new core API class \core_user\fields provides ways to get lists of user fields, and SQL related to * A new core API class \core_user\fields provides ways to get lists of user fields, and SQL related to