MDL-52206 completion: New criteria to handle behaviour between modules

This commit is contained in:
Peter Dias 2020-07-10 12:32:31 +08:00
parent cd52b46e89
commit d975251813
5 changed files with 81 additions and 19 deletions

View File

@ -4,6 +4,15 @@ information provided here is intended especially for developers.
=== 4.0 ===
* New method mark_course_completions_activity_criteria() has been added to mark course completions instantly. It is
based on cron for completion_criteria_activity.php which is refactored to use it as well.
* Modified completion criteria to allow plugins to override core completion logic.
* Core now passes an additional parameter to '_get_completion_state'. This is an array representation of the completion results that have already been
tested. Currently contains - viewed, usegrade, passgrade. Any plugin that are dependent on these criteria can now check this array instead of retesting it.
* Introduced a new plugin function - '_get_completion_aggregation_state', that would indicate the aggregation type/relationship between the plugin and core
completion criteria. This callback should either return a COMPLETION_STANDARD_FLOW / COMPLETION_CUSTOM_MODULE_FLOW. The former for default existing core
behaviour while the latter enforces the override logic from the plugin. Defaults to COMPLETION_STANDARD_FLOW if not defined. This is useful when plugins
need to override the core completion criteria in cases where it may be dependent on them. In these cases, the 'source of truth' would be the response
from the plugin's 'get_completion_state' function. e.g. Quiz's completion defines a criteria of 'requires passing grade OR all attempts AND min attempts
reached.' In these cases, even if a passing grade has not been achieved, the activity should be marked as completed if the no.of attempts have been reached.
=== 3.11 ===
* New Behat steps for activity completion in the behat_completion class:

View File

@ -132,6 +132,16 @@ define('COMPLETION_OR', false);
*/
define('COMPLETION_AND', true);
/**
* When a module implements this, completion state is dependent to the
* module's _get_completion_state callback and activity_custom_completion class.
*/
define('COMPLETION_CUSTOM_MODULE_FLOW', true);
/**
* Standard flow indicates ALL conditions need to be met for completion to be marked as done.
*/
define('COMPLETION_STANDARD_FLOW', false);
/**
* Course completion criteria aggregation method.
*/
@ -686,11 +696,13 @@ class completion_info {
$userid = $USER->id;
}
// Check viewed
if ($cm->completionview == COMPLETION_VIEW_REQUIRED &&
$current->viewed == COMPLETION_NOT_VIEWED) {
return COMPLETION_INCOMPLETE;
$newstate = COMPLETION_COMPLETE;
$completionstate = [];
if ($cm->completionview == COMPLETION_VIEW_REQUIRED) {
$newstate = ($current->viewed == COMPLETION_VIEWED ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE);
$completionstate = [
'viewed' => $newstate
];
}
if ($cm instanceof stdClass) {
@ -706,33 +718,35 @@ class completion_info {
// Make sure we're using a cm_info object.
$cminfo = cm_info::create($cm, $userid);
$newstate = COMPLETION_COMPLETE;
// Check grade
if (!is_null($cminfo->completiongradeitemnumber)) {
$newstate = $this->get_grade_completion($cminfo, $userid);
$completionstate['usegrade'] = $newstate;
if ($cm->completionpassgrade) {
// If we are asking to use pass grade completion but haven't set it,
// If we are asking to use pass grade completion but haven't set it properly,
// then default to COMPLETION_COMPLETE_PASS.
if ($newstate == COMPLETION_COMPLETE) {
return COMPLETION_COMPLETE_PASS;
} else if ($newstate != COMPLETION_COMPLETE_PASS) {
// Mark as incomplete if there is no grade provided or the grade has failed.
$newstate = COMPLETION_COMPLETE_PASS;
}
// The criteria is to mark an activity as complete if a passing grade has been achieved.
// COMPLETION_COMPLETE_FAIL still marks the activity as completed which is incorrect.
// Rectify this.
if ($newstate == COMPLETION_COMPLETE_FAIL) {
$newstate = COMPLETION_INCOMPLETE;
}
} else if ($newstate == COMPLETION_INCOMPLETE) {
return COMPLETION_INCOMPLETE;
$completionstate['passgrade'] = $newstate;
}
}
if (plugin_supports('mod', $cminfo->modname, FEATURE_COMPLETION_HAS_RULES)) {
$response = true;
$cmcompletionclass = activity_custom_completion::get_cm_completion_class($cminfo->modname);
if ($cmcompletionclass) {
/** @var activity_custom_completion $cmcompletion */
$cmcompletion = new $cmcompletionclass($cminfo, $userid);
if ($cmcompletion->get_overall_completion_state() == COMPLETION_INCOMPLETE) {
return COMPLETION_INCOMPLETE;
}
$response = $cmcompletion->get_overall_completion_state() != COMPLETION_INCOMPLETE;
} else {
// Fallback to the get_completion_state callback.
$cmcompletionclass = "mod_{$cminfo->modname}\\completion\\custom_completion";
@ -745,10 +759,34 @@ class completion_info {
debugging("*_get_completion_state() callback functions such as $function have been deprecated and should no " .
"longer be used. Please implement the custom completion class $cmcompletionclass which extends " .
"\core_completion\activity_custom_completion.", DEBUG_DEVELOPER);
if (!$function($this->course, $cminfo, $userid, COMPLETION_AND)) {
return COMPLETION_INCOMPLETE;
}
$response = $function($this->course, $cm, $userid, COMPLETION_AND, $completionstate);
}
// Get the relationship between the core_completion and plugin_completion criteria.
$aggregationtype = COMPLETION_STANDARD_FLOW;
if ($aggregationfn = component_callback_exists("mod_$cminfo->modname", 'get_completion_aggregation_state')) {
$aggregationtype = $aggregationfn();
}
// If the module aggregates using COMPLETION_STANDARD_FLOW, it requires ALL conditions to be met.
// If the aggregation type is COMPLETION_CUSTOM_MODULE_FLOW, completion can be overridden by the plugin.
if (!$response && $aggregationtype == COMPLETION_STANDARD_FLOW) {
return COMPLETION_INCOMPLETE;
} else if ($aggregationtype == COMPLETION_CUSTOM_MODULE_FLOW) {
return ($response ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE);
}
}
if ($completionstate) {
// We have allowed the plugins to do it's thing and run their own checks.
// We have now reached a state where we need to AND all the calculated results.
$newstate = array_reduce($completionstate, function($carry, $value) {
if ($carry == COMPLETION_INCOMPLETE) {
return $carry;
} else {
return $value;
}
}, COMPLETION_COMPLETE);
}
return $newstate;

View File

@ -96,6 +96,9 @@ completely removed from Moodle core too.
fixed units), to always include a non-breaking space between the number and unit, and to use
consistent rounding (always 1 decimal place by default).
* The persistent method get() now returns the correct type for each property defined in the persistent class.
* Require pass grade criteria is now part of core.
Refer to upgrade.php to see transitioning from similar plugin criteria to core
Refer to completion/upgrade.txt for additional information.
=== 3.11.2 ===
* For security reasons, filelib has been updated so all requests now use emulated redirects.

View File

@ -1908,6 +1908,15 @@ function quiz_get_navigation_options() {
);
}
/**
* Get the aggregation state for the module.
*
* @return bool
*/
function quiz_get_completion_aggregation_state() {
return COMPLETION_CUSTOM_MODULE_FLOW;
}
/**
* Check if the module has any update that affects the current user since a given time.
*

View File

@ -1,6 +1,9 @@
This files describes API changes in /mod/* - activity modules,
information provided here is intended especially for developers.
=== 4.0 ===
* A new API function introduced to handle custom completion logic. Refer to completion/upgrade.txt for additional information.
=== 3.9 ===
* The callback get_shortcuts() is now deprecated. Please use get_course_content_items and get_all_content_items instead.