Skip to content

Commit

Permalink
wip: extend completion api to support per-module overrides of the cor…
Browse files Browse the repository at this point in the history
…e rules

- example API change supporting what's needed for core's "require passing grade"
and quiz's "require passing grade or all available attempts exhausted" criteria
to coexist.
  • Loading branch information
snake committed May 6, 2024
1 parent 8054c94 commit 6a765af
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 5 deletions.
16 changes: 16 additions & 0 deletions completion/classes/activity_custom_completion.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ public function manual_completion_always_shown(): bool {
return false;
}

/**
* Returns an array of strings describing core completion criteria which are superseded by module criteria.
*
* This dictates whether the core criteria is AND-ed with module criteria, or whether the module's criteria takes
* precedence. E.g. 'completionview' could be COMPLETION_INCOMPLETE for a given user/instance but a module may
* decide to implement a custom "view or submit" criteria, meaning core's 'completionview' would be superseded by
* the custom module criteria.
*
* If nothing is returned, the default AND-ing of core criteria and module custom criteria will continue to be used.
*
* @return array
*/
public function get_overridden_core_criteria(): array {
return [];
}

/**
* Fetches the module's custom completion class implementation if it's available.
*
Expand Down
2 changes: 1 addition & 1 deletion completion/classes/cm_completion_details.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public function is_overall_complete(): bool {
// Successfull completion states depend on the completion settings.
if (isset($this->completiondata->passgrade)) {
// Passing grade is required. Don't mark it as complete when state is COMPLETION_COMPLETE_FAIL.
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS];
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL];
} else {
// Any grade is required. Mark it as complete even when state is COMPLETION_COMPLETE_FAIL.
$completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL];
Expand Down
39 changes: 35 additions & 4 deletions lib/completionlib.php
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ public function internal_get_state($cm, $userid, $current) {
$cminfo = cm_info::create($cm, $userid);
$completionstate = $this->get_core_completion_state($cminfo, $userid);

$corecriteriaoverrides = [];
if (plugin_supports('mod', $cminfo->modname, FEATURE_COMPLETION_HAS_RULES)) {
$response = true;
$cmcompletionclass = activity_custom_completion::get_cm_completion_class($cminfo->modname);
Expand All @@ -708,19 +709,49 @@ public function internal_get_state($cm, $userid, $current) {
if (!$response) {
return COMPLETION_INCOMPLETE;
}

// Ask the plugin for any core criteria it overrides based on its own custom completion rules.
$corecriteriaoverrides = $cmcompletion->get_overridden_core_criteria();
}

if ($completionstate) {

// Turns:
// ['completiongrade' => 1, 'passgrade' => 0]
// into:
// ['completiongrade' => ['completiongrade' => 1], 'passgrade' => ['passgrade' => 0]]
// to present the keys inside the array_reduce callback, letting us check them for plugin overrides.
array_walk($completionstate, function(&$item, $key){
$item = [$key => $item];
});

// 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.
// Preference for COMPLETION_COMPLETE_PASS over COMPLETION_COMPLETE for proper indication in reports.
$newstate = array_reduce($completionstate, function($carry, $value) {
if (in_array(COMPLETION_INCOMPLETE, [$carry, $value])) {
$newstate = array_reduce($completionstate, function($carry, $value) use ($corecriteriaoverrides, $cm, $userid) {

// The plugin has overridden an in-use core criteria, so ignore it here.
if (in_array(array_key_first($value), $corecriteriaoverrides)) {
// Specific to passgrade, which uses the PASS/FAIL indicators if not for a hidden grade item.
if ('passgrade' === array_key_first($value)) {
// Need a way to know whether to use PASS/FAIL here, which we can't get from just COMPLETION_INCOMPLETE.
if ($item = $this->internal_get_cm_grade_item($cm)) {
if (!$item->hidden) {
if ($grade = $this->internal_get_user_grade($item, $userid)) {
$ispassed = $this->internal_get_is_passing_grade($item, $grade);
return $ispassed ? COMPLETION_COMPLETE_PASS : COMPLETION_COMPLETE_FAIL;
}
}
}
}
return COMPLETION_COMPLETE;
}
if (in_array(COMPLETION_INCOMPLETE, [$carry, reset($value)])) {
return COMPLETION_INCOMPLETE;
} else if (in_array(COMPLETION_COMPLETE_FAIL, [$carry, $value])) {
} else if (in_array(COMPLETION_COMPLETE_FAIL, [$carry, reset($value)])) {
return COMPLETION_COMPLETE_FAIL;
} else {
return in_array(COMPLETION_COMPLETE_PASS, [$carry, $value]) ? COMPLETION_COMPLETE_PASS : $value;
return in_array(COMPLETION_COMPLETE_PASS, [$carry, reset($value)]) ? COMPLETION_COMPLETE_PASS : reset($value);
}

}, COMPLETION_COMPLETE);
Expand Down
12 changes: 12 additions & 0 deletions mod/quiz/classes/completion/custom_completion.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,16 @@ public function get_sort_order(): array {
'completionpassorattemptsexhausted',
];
}

public function get_overridden_core_criteria(): array {

// Quiz overrides the "require passing grade" result if the custom criteria "require passing grade or complete all
// available attempts" is used. In such cases, the core criteria state will NOT be used.
if (!empty($this->cm->completionpassgrade) &&
!empty($this->cm->customdata['customcompletionrules']['completionpassorattemptsexhausted'])) {

return ['passgrade'];
}
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ Feature: Activity completion in the quiz activity with unlocked and re-grading.
Then "Completed (achieved pass grade)" "icon" should not exist in the "Student 1" "table_row"
And I log out
And I am on the "Test quiz name" "quiz activity" page logged in as student1
And I pause
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "failed"

0 comments on commit 6a765af

Please sign in to comment.