Merge branch 'MDL-52206-master-rebased' of git://github.com/peterRd/moodle

This commit is contained in:
Ilya Tregubov 2021-10-07 12:55:12 +07:00
commit b4675942ce
70 changed files with 1670 additions and 188 deletions

View File

@ -273,7 +273,8 @@ class backup_module_structure_step extends backup_structure_step {
'modulename', 'sectionid', 'sectionnumber', 'idnumber',
'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'completion', 'completiongradeitemnumber', 'completionpassgrade',
'completionview', 'completionexpected',
'availability', 'showdescription'));
$tags = new backup_nested_element('tags');
@ -2806,12 +2807,13 @@ class backup_completion_defaults_structure_step extends backup_structure_step {
$cc = new backup_nested_element('course_completion_defaults');
$defaults = new backup_nested_element('course_completion_default', array('id'), array(
'modulename', 'completion', 'completionview', 'completionusegrade', 'completionexpected', 'customrules'
'modulename', 'completion', 'completionview', 'completionusegrade', 'completionpassgrade',
'completionexpected', 'customrules'
));
// Use module name instead of module id so we can insert into another site later.
$sourcesql = "SELECT d.id, m.name as modulename, d.completion, d.completionview, d.completionusegrade,
d.completionexpected, d.customrules
d.completionpassgrade, d.completionexpected, d.customrules
FROM {course_completion_defaults} d join {modules} m on d.module = m.id
WHERE d.course = ?";
$defaults->set_source_sql($sourcesql, array(backup::VAR_COURSEID));

View File

@ -25,8 +25,8 @@ Feature: Award badges based on activity completion
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | attempts | gradepass | completion | completionattemptsexhausted | completionpass | completionusegrade |
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
| activity | name | course | idnumber | attempts | gradepass | completion | completionattemptsexhausted | completionpassgrade | completionusegrade |
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
@ -53,7 +53,8 @@ Feature: Award badges based on activity completion
Scenario: Student earns a badge using activity completion, but does not get passing grade
Given I log in as "student1"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "failed"
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"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "todo"
When I am on the "Test quiz name" "quiz activity" page
And I press "Re-attempt quiz"

View File

@ -17,8 +17,9 @@ Feature: Enable Block Completion in a course using activity completion
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro |
| page | C1 | page1 | Test page name | Test page description |
| activity | course | idnumber | name | intro |
| page | C1 | page1 | Test page name | Test page description |
| assign | C1 | assign1 | Test assign name | Test page description |
Scenario: Add the block to a the course and add course completion items
Given I log in as "teacher1"
@ -65,3 +66,71 @@ Feature: Enable Block Completion in a course using activity completion
And I should see "1 of 1" in the "Activity completion" "table_row"
And I follow "More details"
And I should see "Yes" in the "Activity completion" "table_row"
@javascript
Scenario: Add the block to a the course and add course completion items with passing grade
Given I am on the "Test assign name" "assign activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| gradepass | 50 |
And I press "Save and return to course"
And I am on the "Test assign name" "assign activity" page
And I navigate to "View all submissions" in current page administration
And I click on "Grade" "link" in the "Student" "table_row"
And I set the field "Grade out of 100" to "53"
And I set the field "Notify students" to "0"
And I press "Save changes"
And I am on "Course 1" course homepage with editing mode on
And I add the "Course completion status" block
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test assign name | 1 |
And I press "Save changes"
And I log out
When I am on the "Test assign name" "assign activity" page logged in as student1
And I am on "Course 1" course homepage
Then I should see "Status: Pending" in the "Course completion status" "block"
And I should see "0 of 1" in the "Activity completion" "table_row"
And I trigger cron
And I am on "Course 1" course homepage
And I follow "More details"
And I should see "Achieving grade, Achieving passing grade" in the "Activity completion" "table_row"
And I should see "Yes" in the "Activity completion" "table_row"
@javascript
Scenario: Add the block to a the course and add course completion items with failing grade.
Given I am on the "Test assign name" "assign activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| gradepass | 50 |
And I press "Save and return to course"
And I am on the "Test assign name" "assign activity" page
And I navigate to "View all submissions" in current page administration
And I click on "Grade" "link" in the "Student" "table_row"
And I set the field "Grade out of 100" to "49"
And I set the field "Notify students" to "0"
And I press "Save changes"
And I am on "Course 1" course homepage with editing mode on
And I add the "Course completion status" block
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Test assign name | 1 |
And I press "Save changes"
And I log out
When I am on the "Test assign name" "assign activity" page logged in as student1
And I am on "Course 1" course homepage
Then I should see "Status: Pending" in the "Course completion status" "block"
And I should see "0 of 1" in the "Activity completion" "table_row"
And I trigger cron
And I am on "Course 1" course homepage
And I follow "More details"
And I should see "Achieving grade, Achieving passing grade" in the "Activity completion" "table_row"
And I should see "Yes" in the "Activity completion" "table_row"

View File

@ -40,15 +40,20 @@ abstract class activity_custom_completion {
/** @var int The user's ID. */
protected $userid;
/** @var array The current state of core completion */
protected $completionstate;
/**
* activity_custom_completion constructor.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate The current state of the core completion criteria
*/
public function __construct(cm_info $cm, int $userid) {
public function __construct(cm_info $cm, int $userid, ?array $completionstate = null) {
$this->cm = $cm;
$this->userid = $userid;
$this->completionstate = $completionstate;
}
/**

View File

@ -137,7 +137,8 @@ class core_completion_bulkedit_form extends core_completion_edit_base_form {
// Completion: Don't let them choose automatic completion without turning
// on some conditions.
if (array_key_exists('completion', $data) &&
$data['completion'] == COMPLETION_TRACKING_AUTOMATIC && !empty($data['completionusegrade'])) {
$data['completion'] == COMPLETION_TRACKING_AUTOMATIC &&
(!empty($data['completionusegrade']) || !empty($data['completionpassgrade']))) {
require_once($CFG->libdir.'/gradelib.php');
$moduleswithoutgradeitem = [];
foreach ($this->cms as $cm) {

View File

@ -73,7 +73,11 @@ class cm_completion_details {
$this->returndetails = $returndetails;
$cmcompletionclass = activity_custom_completion::get_cm_completion_class($this->cminfo->modname);
if ($cmcompletionclass) {
$this->cmcompletion = new $cmcompletionclass($this->cminfo, $this->userid);
$this->cmcompletion = new $cmcompletionclass(
$this->cminfo,
$this->userid,
$completioninfo->get_core_completion_state($cminfo, $userid)
);
}
}
@ -128,6 +132,13 @@ class cm_completion_details {
'status' => $status,
'description' => get_string('detail_desc:receivegrade', 'completion'),
];
if (!is_null($this->cminfo->completionpassgrade) && $this->cminfo->completionpassgrade) {
$details['completionpassgrade'] = (object)[
'status' => $completiondata->passgrade ?? COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
];
}
}
if ($this->cmcompletion) {

View File

@ -199,10 +199,19 @@ abstract class core_completion_edit_base_form extends moodleform {
// Automatic completion once it's graded.
if ($this->support_grades()) {
$mform->addElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
$group = [];
$group[] = $mform->createElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
get_string('completionusegrade_desc', 'completion'));
$mform->disabledIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion');
$mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion', '', true);
$group[] = $mform->createElement('advcheckbox', 'completionpassgrade', get_string('completionpassgrade', 'completion'),
get_string('completionpassgrade_desc', 'completion'));
$mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion', '', true);
$mform->disabledIf('completionpassgrade', 'completionusegrade', 'notchecked');
$mform->addGroup($group, 'completionpassgroup', get_string('completionpassgrade', 'completion'), '   ', false);
$mform->disabledIf('completionpassgroup', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$autocompletionpossible = true;
}
@ -247,7 +256,7 @@ abstract class core_completion_edit_base_form extends moodleform {
// on some conditions.
if (array_key_exists('completion', $data) &&
$data['completion'] == COMPLETION_TRACKING_AUTOMATIC) {
if (empty($data['completionview']) && empty($data['completionusegrade']) &&
if (empty($data['completionview']) && empty($data['completionusegrade']) && empty($data['completionpassgrade']) &&
!$this->completion_rule_enabled($data)) {
$errors['completion'] = get_string('badautocompletion', 'completion');
}

View File

@ -176,7 +176,13 @@ class manager {
}
if ($moduledata instanceof cm_info && !is_null($moduledata->completiongradeitemnumber) ||
($moduledata instanceof stdClass && !empty($moduledata->completionusegrade))) {
$activeruledescriptions[] = get_string('completionusegrade_desc', 'completion');
$description = 'completionusegrade_desc';
if (!empty($moduledata->completionpassgrade)) {
$description = 'completionpassgrade_desc';
}
$activeruledescriptions[] = get_string($description, 'completion');
}
// Now, ask the module to provide descriptions for its custom conditional completion rules.
@ -351,8 +357,11 @@ class manager {
protected function apply_completion_cm(\cm_info $cm, $data, $updateinstance) {
global $DB;
$defaults = ['completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
'completionexpected' => 0, 'completiongradeitemnumber' => null];
$defaults = [
'completion' => COMPLETION_DISABLED, 'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
'completionexpected' => 0, 'completiongradeitemnumber' => null,
'completionpassgrade' => 0
];
$data += ['completion' => $cm->completion,
'completionexpected' => $cm->completionexpected,
@ -374,6 +383,8 @@ class manager {
$data['completiongradeitemnumber'] = !empty($data['completionusegrade']) ? 0 : null;
unset($data['completionusegrade']);
} else {
// Completion grade item number is classified in mod_edit forms as 'use grade'.
$data['completionusegrade'] = is_null($cm->completiongradeitemnumber) ? 0 : 1;
$data['completiongradeitemnumber'] = $cm->completiongradeitemnumber;
}
@ -413,7 +424,8 @@ class manager {
'completion' => COMPLETION_DISABLED,
'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
'completionexpected' => 0,
'completionusegrade' => 0
'completionusegrade' => 0,
'completionpassgrade' => 0
];
$data = (array)$data;
@ -470,7 +482,7 @@ class manager {
public static function get_default_completion($course, $module, $flatten = true) {
global $DB, $CFG;
if ($data = $DB->get_record('course_completion_defaults', ['course' => $course->id, 'module' => $module->id],
'completion, completionview, completionexpected, completionusegrade, customrules')) {
'completion, completionview, completionexpected, completionusegrade, completionpassgrade, customrules')) {
if ($data->customrules && ($customrules = @json_decode($data->customrules, true))) {
if ($flatten) {
foreach ($customrules as $key => $value) {

View File

@ -240,6 +240,10 @@ class completion_criteria_activity extends completion_criteria {
if (!is_null($cm->completiongradeitemnumber)) {
$details['requirement'][] = get_string('achievinggrade', 'completion');
}
if ($cm->completionpassgrade) {
$details['requirement'][] = get_string('achievingpassinggrade', 'completion');
}
}
$details['requirement'] = implode(', ', $details['requirement']);

View File

@ -1,14 +1,10 @@
@core @core_completion
@core @core_completion @javascript
Feature: Allow teachers to bulk edit activity completion rules in a course.
In order to avoid editing single activities
As a teacher
I need to be able to edit the completion rules for a group of activities.
# Given I am a teacher in a course with completion tracking enabled and activities present.
# When I bulk edit activity completion rules for activities of the same kind.
# Then the completion rules should be updated for all selected activities.
@javascript
Scenario: Bulk edit activity completion rules
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
@ -32,6 +28,14 @@ Feature: Allow teachers to bulk edit activity completion rules in a course.
And I set the following fields to these values:
| Enable completion tracking | Yes |
And I press "Save and display"
And I log out
# Given I am a teacher in a course with completion tracking enabled and activities present.
# When I bulk edit activity completion rules for activities of the same kind.
# Then the completion rules should be updated for all selected activities.
Scenario: Bulk edit activity completion rules
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I navigate to "Course completion" in current page administration
And I follow "Bulk edit activity completion"
And I click on "Test assignment one" "checkbox"
@ -56,3 +60,29 @@ Feature: Allow teachers to bulk edit activity completion rules in a course.
And I should see "Student must receive a grade to complete this activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "Student must submit to this activity to complete it" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
# Same conditions as above,
# However if completionpassgrade is set, only the completionpassgrade detail should be shown.
# It is implied requires grade is selected as it passgrade is dependent on it.
Scenario: Bulk edit passing grade completion
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I navigate to "Course completion" in current page administration
And I follow "Bulk edit activity completion"
And I click on "Test assignment one" "checkbox"
And I click on "Test assignment two" "checkbox"
And I click on "Edit" "button"
And I should see "Completion tracking"
And I should see "The changes will affect the following 2 activities or resources:"
And I should see "Student must submit to this activity to complete it"
And I select "Show activity as complete when conditions are met" from the "completion" singleselect
And I click on "completionusegrade" "checkbox"
And I click on "completionpassgrade" "checkbox"
And I click on "Save changes" "button"
Then I should see "Changes saved"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "Student must receive a passing grade to complete this activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment one']]" "xpath_element"
And I should see "With conditions" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should see "Student must receive a passing grade to complete this activity" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"
And I should not see "Completion expected on" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' row ')][.//*[text() = 'Test assignment two']]" "xpath_element"

View File

@ -0,0 +1,52 @@
@core @core_completion
Feature: Students will be marked as completed if they have achieved a passing grade.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activity" exists:
| idnumber | a1 |
| activity | assign |
| course | C1 |
| name | Test assignment name |
| intro | Submit your online text |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionpassgrade | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Student First" user has not completed "Test assignment name" activity
And I log out
Scenario: Passing grade completion
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I press "Save changes"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I log out
And I log in as "student2"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"

View File

@ -0,0 +1,69 @@
@core @core_completion
Feature: Students will be marked as completed and pass/fail
if they have viewed an activity and achieved a grade.
Background:
Given the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | Frist | teacher1@example.com |
| student1 | Student | First | student1@example.com |
| student2 | Student | Second | student2@example.com |
| student3 | Student | Third | student3@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "activity" exists:
| activity | assign |
| course | C1 |
| idnumber | a1 |
| name | Test assignment name |
| intro | Submit your online text |
| assignsubmission_onlinetext_enabled | 1 |
| assignsubmission_file_enabled | 0 |
| completion | 2 |
| completionview | 1 |
| completionusegrade | 1 |
| gradepass | 50 |
| completionpassgrade | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Student First" user has not completed "Test assignment name" activity
And I log out
And I am on the "Test assignment name" "assign activity" page logged in as student2
And I log out
And I am on the "Test assignment name" "assign activity" page logged in as student1
And I log out
Scenario: Confirm completion (incomplete/pass/fail) are set correctly
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
And I give the grade "30" to the user "Student Third" for the grade item "Test assignment name"
And I press "Save changes"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And the "View" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
And I log out
And I log in as "student2"
And I am on "Course 1" course homepage
And the "View" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done"
And I log out
And I log in as "student3"
And I am on "Course 1" course homepage
And the "View" completion condition of "Test assignment name" is displayed as "todo"
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"

View File

@ -80,7 +80,7 @@ class core_completion_bulk_update_testcase extends advanced_testcase {
'lti-2' => ['lti', ['completion' => COMPLETION_TRACKING_MANUAL]],
'page-1' => ['page', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'page-2' => ['page', ['completion' => COMPLETION_TRACKING_MANUAL]],
'quiz-1' => ['quiz', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionpass' => 1]],
'quiz-1' => ['quiz', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionpassgrade' => 1]],
'quiz-2' => ['quiz', ['completion' => COMPLETION_TRACKING_MANUAL]],
'resource-1' => ['resource', ['completion' => COMPLETION_TRACKING_AUTOMATIC, 'completionview' => 1]],
'resource-2' => ['resource', ['completion' => COMPLETION_TRACKING_MANUAL]],

View File

@ -93,6 +93,7 @@ class cm_completion_details_test extends advanced_testcase {
['modname', $modname],
['completionview', $completionoptions['completionview'] ?? COMPLETION_VIEW_NOT_REQUIRED],
['completiongradeitemnumber', $completionoptions['completionusegrade'] ?? null],
['completionpassgrade', $completionoptions['completionpassgrade'] ?? null],
]));
return new cm_completion_details($this->completioninfo, $mockcminfo, 2);
@ -192,13 +193,13 @@ class cm_completion_details_test extends advanced_testcase {
public function get_details_provider() {
return [
'No completion tracking' => [
COMPLETION_TRACKING_NONE, null, null, []
COMPLETION_TRACKING_NONE, null, null, null, []
],
'Manual completion tracking' => [
COMPLETION_TRACKING_MANUAL, null, null, []
COMPLETION_TRACKING_MANUAL, null, null, null, []
],
'Automatic, require view, not viewed' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, null, null, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
@ -206,7 +207,7 @@ class cm_completion_details_test extends advanced_testcase {
]
],
'Automatic, require view, viewed' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, null, null, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
@ -214,7 +215,7 @@ class cm_completion_details_test extends advanced_testcase {
]
],
'Automatic, require grade, incomplete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_INCOMPLETE, null, [
'completionusegrade' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
@ -222,7 +223,7 @@ class cm_completion_details_test extends advanced_testcase {
]
],
'Automatic, require grade, complete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, null, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
@ -230,7 +231,7 @@ class cm_completion_details_test extends advanced_testcase {
]
],
'Automatic, require view (complete) and grade (incomplete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, null, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
@ -242,7 +243,7 @@ class cm_completion_details_test extends advanced_testcase {
]
],
'Automatic, require view (incomplete) and grade (complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, null, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
@ -253,6 +254,62 @@ class cm_completion_details_test extends advanced_testcase {
]
]
],
'Automatic, require grade, require pass grade, complete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require grade, require pass grade, incomplete' => [
COMPLETION_TRACKING_AUTOMATIC, null, COMPLETION_COMPLETE, COMPLETION_INCOMPLETE, [
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require view (complete), require grade(complete), require pass grade(complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_COMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionview' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
'Automatic, require view (incomplete), require grade(complete), require pass grade(complete)' => [
COMPLETION_TRACKING_AUTOMATIC, COMPLETION_INCOMPLETE, COMPLETION_COMPLETE, COMPLETION_COMPLETE, [
'completionview' => (object)[
'status' => COMPLETION_INCOMPLETE,
'description' => get_string('detail_desc:view', 'completion'),
],
'completionusegrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivegrade', 'completion'),
],
'completionpassgrade' => (object)[
'status' => COMPLETION_COMPLETE,
'description' => get_string('detail_desc:receivepassgrade', 'completion'),
],
]
],
];
}
@ -263,13 +320,16 @@ class cm_completion_details_test extends advanced_testcase {
* @param int $completion The completion tracking mode.
* @param int|null $completionview Completion status of the "view" completion condition.
* @param int|null $completiongrade Completion status of the "must receive grade" completion condition.
* @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition.
* @param array $expecteddetails Expected completion details returned by get_details().
*/
public function test_get_details(int $completion, ?int $completionview, ?int $completiongrade, array $expecteddetails) {
public function test_get_details(int $completion, ?int $completionview,
?int $completiongrade, ?int $completionpassgrade, array $expecteddetails) {
$options = [];
$getdatareturn = (object)[
'viewed' => $completionview,
'completiongrade' => $completiongrade,
'passgrade' => $completionpassgrade,
];
if (!is_null($completionview)) {
@ -278,13 +338,16 @@ class cm_completion_details_test extends advanced_testcase {
if (!is_null($completiongrade)) {
$options['completionusegrade'] = true;
}
if (!is_null($completionpassgrade)) {
$options['completionpassgrade'] = true;
}
$cmcompletion = $this->setup_data($completion, $options, $getdatareturn);
$this->assertEquals($expecteddetails, $cmcompletion->get_details());
}
/**
* Data provider for test_get_details().
* Data provider for test_get_details_custom_order().
* @return array[]
*/
public function get_details_custom_order_provider() {

View File

@ -122,6 +122,15 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
['course' => $course->id],
['availability' => $availability],
);
$assignautocompletion = $this->getDataGenerator()->create_module('assign',
['course' => $course->id], [
'showdescription' => true,
'completionview' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completiongradeitemnumber' => 1,
'completionpassgrade' => 1,
],
);
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
array('completion' => 1, 'visible' => 0));
@ -151,11 +160,11 @@ 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 5 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 5;
// We added 6 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 6;
$numberofhidden = 1;
$numberofcompletions = $numberofactivities - $numberofhidden;
$numberofstatusstudent = 3;
$numberofstatusstudent = 4;
$this->assertCount($numberofstatusstudent, $result['statuses']);
@ -186,6 +195,26 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertEquals('completionview', $details[0]['rulename']);
$this->assertEquals(0, $details[0]['rulevalue']['status']);
} else if ($status['cmid'] == $assignautocompletion->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(3, $details);
$expecteddetails = [
'completionview',
'completionusegrade',
'completionpassgrade',
];
foreach ($expecteddetails as $index => $name) {
$this->assertEquals($name, $details[$index]['rulename']);
$this->assertEquals(0, $details[$index]['rulevalue']['status']);
}
} else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
@ -200,7 +229,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertCount(0, $details);
}
}
$this->assertEquals(3, $activitiesfound);
$this->assertEquals(4, $activitiesfound);
// Teacher should see students status, they are in different groups but the teacher can access all groups.
$this->setUser($teacher);
@ -248,7 +277,7 @@ 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) {
} else if (in_array($status['cmid'], [$forumautocompletion->cmid, $assignautocompletion->cmid])) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
@ -258,7 +287,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
}
}
$this->assertEquals(4, $activitiesfound);
$this->assertEquals(5, $activitiesfound);
// Change teacher role capabilities (disable access all groups).
$context = context_course::instance($course->id);

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

@ -2867,6 +2867,7 @@ class core_course_external extends external_api {
'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
'completionpassgrade' => new external_value(PARAM_INT, 'Completion pass grade setting', VALUE_OPTIONAL),
'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),

View File

@ -461,6 +461,7 @@ function get_array_of_activities($courseid) {
$mod[$seq]->extra = "";
$mod[$seq]->completiongradeitemnumber =
$rawmods[$seq]->completiongradeitemnumber;
$mod[$seq]->completionpassgrade = $rawmods[$seq]->completionpassgrade;
$mod[$seq]->completionview = $rawmods[$seq]->completionview;
$mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
$mod[$seq]->showdescription = $rawmods[$seq]->showdescription;

View File

@ -72,6 +72,7 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
$completion = new completion_info($course);
if ($completion->is_enabled()) {
$newcm->completion = $moduleinfo->completion;
$newcm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
if ($moduleinfo->completiongradeitemnumber === '') {
$newcm->completiongradeitemnumber = null;
} else {
@ -432,6 +433,8 @@ function set_moduleinfo_defaults($moduleinfo) {
if (isset($moduleinfo->completionusegrade) && $moduleinfo->completionusegrade) {
$moduleinfo->completiongradeitemnumber = 0;
} else if (!isset($moduleinfo->completiongradeitemnumber)) {
// If there is no gradeitemnumber set, make sure to disable completionpassgrade.
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completiongradeitemnumber = null;
}
@ -544,6 +547,7 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
// the activity may be locked; if so, these should not be updated.
if (!empty($moduleinfo->completionunlocked)) {
$cm->completion = $moduleinfo->completion;
$cm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
if ($moduleinfo->completiongradeitemnumber === '') {
$cm->completiongradeitemnumber = null;
} else {
@ -712,6 +716,7 @@ function get_moduleinfo_data($cm, $course) {
$data->completionview = $cm->completionview;
$data->completionexpected = $cm->completionexpected;
$data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
$data->completionpassgrade = $cm->completionpassgrade;
$data->completiongradeitemnumber = $cm->completiongradeitemnumber;
$data->showdescription = $cm->showdescription;
$data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);

View File

@ -370,6 +370,15 @@ abstract class moodleform_mod extends moodleform {
if ($mform->elementExists('completionusegrade')) {
$mform->freeze('completionusegrade');
}
if ($mform->elementExists('completionpassgrade')) {
$mform->freeze('completionpassgrade');
// Has the completion pass grade completion criteria been set?
// If it has then we shouldn't change the gradepass field.
if ($mform->exportValue('completionpassgrade')) {
$mform->freeze('gradepass');
}
}
if ($mform->elementExists('completiongradeitemnumber')) {
$mform->freeze('completiongradeitemnumber');
}
@ -468,6 +477,28 @@ abstract class moodleform_mod extends moodleform {
);
}
}
if (isset($data['completionpassgrade']) && $data['completionpassgrade']) {
// We need to check whether there's a valid gradepass value.
// This can either be in completiongradeitemnumber when there are multiple options OR,
// The first grade item if completionusegrade is specified.
$validategradepass = false;
if (isset($data['completiongradeitemnumber'])) {
if ($data['completiongradeitemnumber'] == (string)$itemnumber) {
$validategradepass = true;
}
} else if (isset($data['completionusegrade']) && $data['completionusegrade']) {
$validategradepass = true;
}
// Confirm gradepass is a valid non-zero value.
if ($validategradepass && (!isset($data[$gradepassfieldname]) || grade_floatval($data[$gradepassfieldname]) == 0)) {
$errors['completionpassgrade'] = get_string(
'activitygradetopassnotset',
'completion'
);
}
}
}
// Completion: Don't let them choose automatic completion without turning
@ -482,7 +513,7 @@ abstract class moodleform_mod extends moodleform {
$rulesenabled = !empty($data['completionview']);
// Use grade to complete (only one grade item).
$rulesenabled = $rulesenabled || !empty($data['completionusegrade']);
$rulesenabled = $rulesenabled || !empty($data['completionusegrade']) || !empty($data['completionpassgrade']);
// Use grade to complete (specific grade item).
if (!$rulesenabled && isset($data['completiongradeitemnumber'])) {
@ -724,16 +755,28 @@ abstract class moodleform_mod extends moodleform {
$mform->hideIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion');
// Complete if the user has reached the pass grade.
$mform->addElement(
'checkbox',
'completionpassgrade', null,
get_string('completionpassgrade_desc', 'completion')
);
$mform->hideIf('completionpassgrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->disabledIf('completionpassgrade', 'completionusegrade', 'notchecked');
$mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion');
// The disabledIf logic differs between ratings and other grade items due to different field types.
if ($this->_features->rating) {
// If using the rating system, there is no grade unless ratings are enabled.
$mform->disabledIf('completionusegrade', 'assessed', 'eq', 0);
$mform->disabledIf('completionpassgrade', 'assessed', 'eq', 0);
} else {
// All other field types use the '$gradefieldname' field's modgrade_type.
$itemnumbers = array_keys($itemnames);
$itemnumber = array_shift($itemnumbers);
$gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade');
$mform->disabledIf('completionusegrade', "{$gradefieldname}[modgrade_type]", 'eq', 'none');
$mform->disabledIf('completionpassgrade', "{$gradefieldname}[modgrade_type]", 'eq', 'none');
}
} else if (count($itemnames) > 1) {
// There are multiple grade items in this activity.
@ -752,6 +795,16 @@ abstract class moodleform_mod extends moodleform {
$options
);
$mform->hideIf('completiongradeitemnumber', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
// Complete if the user has reached the pass grade.
$mform->addElement(
'checkbox',
'completionpassgrade', null,
get_string('completionpassgrade_desc', 'completion')
);
$mform->hideIf('completionpassgrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
$mform->disabledIf('completionpassgrade', 'completiongradeitemnumber', 'eq', '');
$mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion');
}
}

View File

@ -221,6 +221,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
$moduleinfo->completiongradeitemnumber = 1;
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
// Conditional activity.
@ -283,6 +284,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
@ -504,6 +506,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
$moduleinfo->completiongradeitemnumber = 1;
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
$moduleinfo->completionunlocked = 1;
@ -561,6 +564,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);

View File

@ -2306,14 +2306,17 @@ class externallib_test extends externallib_advanced_testcase {
$this->resetAfterTest(true);
$this->setAdminUser();
$course = self::getDataGenerator()->create_course();
$course = self::getDataGenerator()->create_course(['enablecompletion' => 1]);
$record = array(
'course' => $course->id,
'name' => 'First Assignment'
);
$options = array(
'idnumber' => 'ABC',
'visible' => 0
'visible' => 0,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completiongradeitemnumber' => 0,
'completionpassgrade' => 1,
);
// Hidden activity.
$assign = self::getDataGenerator()->create_module('assign', $record, $options);
@ -2370,7 +2373,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
$this->assertCount(28, $result['cm']);
$this->assertCount(29, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
$this->assertEquals(100, $result['cm']['grade']);
@ -2438,7 +2441,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
$this->assertCount(26, $result['cm']);
$this->assertCount(27, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($record['grade'], $result['cm']['grade']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);

View File

@ -112,6 +112,7 @@ class core_course_modlib_testcase extends advanced_testcase {
$expecteddata->completionview = $assigncm->completionview;
$expecteddata->completionexpected = $assigncm->completionexpected;
$expecteddata->completionusegrade = is_null($assigncm->completiongradeitemnumber) ? 0 : 1;
$expecteddata->completionpassgrade = $assigncm->completionpassgrade;
$expecteddata->completiongradeitemnumber = null;
$expecteddata->showdescription = $assigncm->showdescription;
$expecteddata->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $assigncm->id);
@ -138,6 +139,7 @@ class core_course_modlib_testcase extends advanced_testcase {
}
}
$expecteddata->gradepass = '0.00';
$expecteddata->completionpassgrade = $assigncm->completionpassgrade;
// Unset untestable.
unset($expecteddata->cmid);

View File

@ -25,6 +25,7 @@
*/
$string['achievinggrade'] = 'Achieving grade';
$string['achievingpassinggrade'] = 'Achieving passing grade';
$string['activities'] = 'Activities';
$string['activitieslabel'] = 'Activities / resources';
$string['activityaggregation'] = 'Condition requires';
@ -35,6 +36,7 @@ $string['activitiescompletednote'] = 'Note: Activity completion must be set for
$string['activitycompletion'] = 'Activity completion';
$string['activitycompletionupdated'] = 'Changes saved';
$string['activitygradenotrequired'] = 'Grade not required';
$string['activitygradetopassnotset'] = 'This activity does not have a valid grade to pass set. It may be set in the Grade section of the activity settings.';
$string['affectedactivities'] = 'The changes will affect the following <b>{$a}</b> activities or resources:';
$string['aggregationmethod'] = 'Aggregation method';
$string['all'] = 'All';
@ -106,6 +108,9 @@ $string['completionondate'] = 'Date';
$string['completionondatevalue'] = 'Date when course will be marked as complete';
$string['completionduration'] = 'Enrolment';
$string['completionsettingslocked'] = 'Completion settings locked';
$string['completionpassgrade'] = 'Require passing grade';
$string['completionpassgrade_desc'] = 'Student must receive a passing grade to complete this activity';
$string['completionpassgrade_help'] = 'If enabled, the activity is considered complete when a student receives a passing grade.';
$string['completionusegrade'] = 'Require grade';
$string['completionusegrade_desc'] = 'Student must receive a grade to complete this activity';
$string['completionusegrade_help'] = 'If enabled, the activity is considered complete when a student receives a grade. If a pass grade for the activity is set, then pass and fail icons are displayed in the activity completion report.';
@ -141,6 +146,7 @@ $string['deletecompletiondata'] = 'Delete completion data';
$string['dependencies'] = 'Dependencies';
$string['dependenciescompleted'] = 'Completion of other courses';
$string['detail_desc:receivegrade'] = 'Receive a grade';
$string['detail_desc:receivepassgrade'] = 'Receive a passing grade';
$string['detail_desc:view'] = 'View';
$string['hiddenrules'] = 'Some settings specific to <b>{$a}</b> have been hidden. To view unselect other activities';
$string['editcoursecompletionsettings'] = 'Edit course completion settings';

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,13 +696,7 @@ 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;
if ($cm instanceof stdClass) {
// Modname hopefully is provided in $cm but just in case it isn't, let's grab it.
if (!isset($cm->modname)) {
@ -705,25 +709,15 @@ 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);
if ($newstate == COMPLETION_INCOMPLETE) {
return COMPLETION_INCOMPLETE;
}
}
$completionstate = $this->get_core_completion_state($cminfo, $userid);
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;
}
$cmcompletion = new $cmcompletionclass($cminfo, $userid, $completionstate);
$response = $cmcompletion->get_overall_completion_state() != COMPLETION_INCOMPLETE;
} else {
// Fallback to the get_completion_state callback.
$cmcompletionclass = "mod_{$cminfo->modname}\\completion\\custom_completion";
@ -736,10 +730,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;
@ -1120,6 +1138,51 @@ class completion_info {
return (object)$cacheddata[$cminfo->id];
}
/**
* Get the latest completion state for each criteria used in the module
*
* @param cm_info $cm The corresponding module's information
* @param int $userid The id for the user we are calculating core completion state
* @return array $data The individualised core completion state used in the module.
* Consists of the following keys completiongrade, passgrade, viewed
*/
public function get_core_completion_state(cm_info $cm, int $userid): array {
global $DB;
$data = [];
// Include in the completion info the grade completion, if necessary.
if (!is_null($cm->completiongradeitemnumber)) {
$newstate = $this->get_grade_completion($cm, $userid);
$data['completiongrade'] = $newstate;
if ($cm->completionpassgrade) {
// 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) {
$newstate = COMPLETION_COMPLETE_PASS;
}
// The activity is using 'passing grade' criteria therefore fail indication should be on this criteria.
// The user has received a (failing) grade so 'completiongrade' should properly indicate this.
if ($newstate == COMPLETION_COMPLETE_FAIL) {
$data['completiongrade'] = COMPLETION_COMPLETE;
}
$data['passgrade'] = $newstate;
}
}
// If view is required, try and fetch from the db. In some cases, cache can be invalid.
if ($cm->completionview == COMPLETION_VIEW_REQUIRED) {
$data['viewed'] = COMPLETION_INCOMPLETE;
$record = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cm->id, 'userid' => $userid));
if ($record) {
$data['viewed'] = ($record->viewed == COMPLETION_VIEWED ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE);
}
}
return $data;
}
/**
* Adds the user's custom completion data on the given course module.
*
@ -1128,12 +1191,7 @@ class completion_info {
* @return array The additional completion data.
*/
protected function get_other_cm_completion_data(cm_info $cm, int $userid): array {
$data = [];
// Include in the completion info the grade completion, if necessary.
if (!is_null($cm->completiongradeitemnumber)) {
$data['completiongrade'] = $this->get_grade_completion($cm, $userid);
}
$data = $this->get_core_completion_state($cm, $userid);
// Custom activity module completion data.
@ -1153,7 +1211,7 @@ class completion_info {
}
/** @var activity_custom_completion $customcmcompletion */
$customcmcompletion = new $cmcompletionclass($cm, $userid);
$customcmcompletion = new $cmcompletionclass($cm, $userid, $data);
foreach ($customdata['customcompletionrules'] as $rule => $enabled) {
if (!$enabled) {
// Skip inactive completion rules.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20210728" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20211001" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -304,6 +304,7 @@
<FIELD NAME="completiongradeitemnumber" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Grade-item number used to track automatic completion, if applicable."/>
<FIELD NAME="completionview" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Controls whether a page view is part of the automatic completion requirements for this activity. 0 = view not required 1 = view required"/>
<FIELD NAME="completionexpected" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Date at which students are expected to complete this activity. This field is used when displaying student progress."/>
<FIELD NAME="completionpassgrade" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Enable completion check on passing grade."/>
<FIELD NAME="showdescription" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Some module types support a 'description' which shows within the module pages. This option controls whether it also displays on the course main page. 0 = does not display (default), 1 = displays"/>
<FIELD NAME="availability" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Availability restrictions for viewing this activity, in JSON format. Null if no restrictions."/>
<FIELD NAME="deletioninprogress" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@ -3866,6 +3867,7 @@
<FIELD NAME="completion" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionview" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionusegrade" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionpassgrade" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionexpected" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="customrules" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>

View File

@ -2892,5 +2892,34 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2021100600.01);
}
if ($oldversion < 2021100600.02) {
$table = new xmldb_table('course_completion_defaults');
// Adding fields to table course_completion_defaults.
$field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null,
XMLDB_NOTNULL, null, '0', 'completionusegrade');
// Conditionally launch add field for course_completion_defaults.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_main_savepoint(true, 2021100600.02);
}
if ($oldversion < 2021100600.03) {
$table = new xmldb_table('course_modules');
// Adding new fields to table course_module table.
$field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null,
XMLDB_NOTNULL, null, '0', 'completionexpected');
// Conditionally launch create table for course_completion_defaults.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_main_savepoint(true, 2021100600.03);
}
return true;
}

View File

@ -970,6 +970,12 @@ class cm_info implements IteratorAggregate {
*/
private $completiongradeitemnumber;
/**
* 1 if pass grade completion is enabled, 0 otherwise - from course_modules table
* @var int
*/
private $completionpassgrade;
/**
* 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
* @var int
@ -1173,6 +1179,7 @@ class cm_info implements IteratorAggregate {
'completion' => false,
'completionexpected' => false,
'completiongradeitemnumber' => false,
'completionpassgrade' => false,
'completionview' => false,
'conditionscompletion' => false,
'conditionsfield' => false,
@ -1648,7 +1655,7 @@ class cm_info implements IteratorAggregate {
// Standard fields from table course_modules.
static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'completionpassgrade',
'showdescription', 'availability', 'deletioninprogress');
foreach ($cmfields as $key) {
$cmrecord->$key = $this->$key;
@ -1867,6 +1874,7 @@ class cm_info implements IteratorAggregate {
// availability and completion fields, even if availability or completion
// are actually disabled
$this->completion = isset($mod->completion) ? $mod->completion : 0;
$this->completionpassgrade = isset($mod->completionpassgrade) ? $mod->completionpassgrade : 0;
$this->completiongradeitemnumber = isset($mod->completiongradeitemnumber)
? $mod->completiongradeitemnumber : null;
$this->completionview = isset($mod->completionview)

View File

@ -176,7 +176,7 @@ abstract class testing_module_generator extends component_generator_base {
$easymergefields = array('section', 'added', 'score', 'indent',
'visible', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'availability', 'showdescription');
'completionpassgrade', 'availability', 'showdescription');
foreach ($easymergefields as $key) {
if (isset($options[$key])) {
$moduleinfo->$key = $options[$key];
@ -195,6 +195,7 @@ abstract class testing_module_generator extends component_generator_base {
'completion' => 0,
'completionview' => 0,
'completionexpected' => 0,
'completionpassgrade' => 0,
'conditiongradegroup' => array(),
'conditionfieldgroup' => array(),
'conditioncompletiongroup' => array()

View File

@ -258,6 +258,7 @@ class core_test_generator_testcase extends advanced_testcase {
'completion' => COMPLETION_TRACKING_AUTOMATIC, // "Show activity as complete when conditions are met."
'completionview' => 1, // "Student must view this activity to complete it"
'completionusegrade' => 1, // "Student must receive a grade to complete this activity"
'completionpassgrade' => 1, // "Student must receive a passing grade to complete this activity"
);
// Module supports FEATURE_RATE:
@ -327,6 +328,7 @@ class core_test_generator_testcase extends advanced_testcase {
$cm3 = $modinfo->cms[$m3->cmid];
$this->assertEquals($featurecompletionautomatic['completion'], $cm3->completion);
$this->assertEquals($featurecompletionautomatic['completionview'], $cm3->completionview);
$this->assertEquals($featurecompletionautomatic['completionpassgrade'], $cm3->completionpassgrade);
$this->assertEquals(0, $cm3->completiongradeitemnumber); // Zero instead of default null since 'completionusegrade' was set.
$gradingitem = grade_item::fetch(array('courseid'=>$course->id, 'itemtype'=>'mod', 'itemmodule' => 'assign', 'iteminstance' => $m3->id));
$this->assertEquals(0, $gradingitem->grademin);

View File

@ -725,6 +725,7 @@ class core_completionlib_testcase extends advanced_testcase {
'completion' => COMPLETION_TRACKING_AUTOMATIC,
// Submission grade required.
'completiongradeitemnumber' => 0,
'completionpassgrade' => 1,
]);
$cmworkshop = cm_info::create(get_coursemodule_from_instance('workshop', $workshop->id));
@ -756,8 +757,10 @@ class core_completionlib_testcase extends advanced_testcase {
$workshopcompletiondata = $method->invoke($completioninfo, $cmworkshop, $user->id);
$this->assertArrayHasKey('completiongrade', $workshopcompletiondata);
$this->assertArrayHasKey('passgrade', $workshopcompletiondata);
$this->assertArrayNotHasKey('customcompletion', $workshopcompletiondata);
$this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['completiongrade']);
$this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['passgrade']);
// Check that fetching data for a module with no completion conditions does not provide any data.
$choice2completiondata = $method->invoke($completioninfo, $cmchoice2, $user->id);

View File

@ -183,6 +183,7 @@ class modinfolib_test extends advanced_testcase {
$this->assertEquals($moduledb->indent, $cm->indent);
$this->assertEquals($moduledb->completion, $cm->completion);
$this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
$this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
$this->assertEquals($moduledb->completionview, $cm->completionview);
$this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
$this->assertEquals($moduledb->showdescription, $cm->showdescription);

View File

@ -103,6 +103,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

@ -89,6 +89,7 @@ class custom_completion extends activity_custom_completion {
'completionview',
'completionsubmit',
'completionusegrade',
'completionpassgrade',
];
}
}

View File

@ -80,6 +80,7 @@ class custom_completion extends activity_custom_completion {
'completionview',
'completionentries',
'completionusegrade',
'completionpassgrade',
];
}
}

View File

@ -0,0 +1,177 @@
@mod @mod_data @core_completion
Feature: Completion pass grade
View activity completion in the database activity
In order to have visibility of database completion requirements
As a student
I need to be able to view my database completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And the following "activity" exists:
| activity | data |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
And I am on the "Music history" "data activity" page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Aggregate type | Average of ratings |
| scale[modgrade_type] | Point |
| scale[modgrade_point] | 100 |
| gradepass | 50 |
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
| Require grade | 1 |
| completionpassgrade | 1 |
| completionentriesenabled | 1 |
| completionentries | 2 |
And I press "Save and display"
And I add a "Text input" field to "Music history" database and I fill the form with:
| Field name | Instrument types |
And I follow "Templates"
And I press "Save template"
And I log out
Scenario: View automatic completion items as a teacher and confirm all tabs display conditions
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Music history"
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "View single"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Search"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Add entry"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Export"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Templates"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Fields"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I follow "Presets"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 2" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
@javascript
Scenario: View automatic completion items as a failing student
Given I am on the "Music history" "data activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I am on "Course 1" course homepage
And I add an entry to "Music history" database with:
| Instrument types | Drums |
And I press "Save and view"
# One entry is not enough to mark as complete.
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I am on "Course 1" course homepage
And I add an entry to "Music history" database with:
| Instrument types | Hurdygurdy |
And I press "Save and view"
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "data activity" page logged in as teacher1
And I follow "View single"
And I set the field "rating" to "3"
And I log out
When I am on the "Music history" "data activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
@javascript
Scenario: View automatic completion items as a passing student
Given I am on the "Music history" "data activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I am on "Course 1" course homepage
And I add an entry to "Music history" database with:
| Instrument types | Drums |
And I press "Save and view"
# One entry is not enough to mark as complete.
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I am on "Course 1" course homepage
And I add an entry to "Music history" database with:
| Instrument types | Hurdygurdy |
And I press "Save and view"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "data activity" page logged in as teacher1
And I follow "View single"
And I set the field "rating" to "60"
And I log out
When I am on the "Music history" "data activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 2" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity

View File

@ -112,6 +112,7 @@ class custom_completion extends activity_custom_completion {
'completionreplies',
'completionposts',
'completionusegrade',
'completionpassgrade',
];
}
}

View File

@ -0,0 +1,151 @@
@mod @mod_forum @core_completion
Feature: Completion pass grade view activity completion in the forum activity
In order to have visibility of forum completion requirements
As a student
I need to be able to view my forum completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And the following "activity" exists:
| activity | forum |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
And I am on the "Music history" "forum activity" page
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Whole forum grading > Type | Point |
| Whole forum grading > Grade to pass | 50 |
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
| Require grade | Whole forum |
| completionpostsenabled | 1 |
| completionpassgrade | 1 |
| completionposts | 2 |
| completiondiscussionsenabled | 1 |
| completiondiscussions | 1 |
| completionrepliesenabled | 1 |
| completionreplies | 1 |
And I press "Save and display"
And I log out
Scenario: View automatic completion items as a teacher
Given I am on the "Music history" "forum activity" page logged in as teacher1
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Start discussions: 1" completion condition
And "Music history" should have the "Make forum posts: 2" completion condition
And "Music history" should have the "Post replies: 1" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
@javascript
Scenario: View automatic completion items as a failing student
Given I am on the "Music history" "forum activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "todo"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "todo"
And the "Post replies: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I add a new discussion to "Music history" forum with:
| Subject | Fun instruments |
| Message | I like drums |
And I am on the "Music history" "forum activity" page
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "todo"
And the "Post replies: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I reply "Fun instruments" post from "Music history" forum with:
| Subject | Reply 1 to Fun instruments |
| Message | Guitar is also Fun |
And I am on the "Music history" "forum activity" page
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "done"
And the "Post replies: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
# Grade the student
And I am on the "Music history" "forum activity" page logged in as teacher1
And I press "Grade users"
And I set the field "grade" to "33"
And I press "Save"
And I press "Close grader"
And I log out
# All conditions should now be completed.
When I am on the "Music history" "forum activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "done"
And the "Post replies: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
@javascript
Scenario: View automatic completion items as a passing student
Given I am on the "Music history" "forum activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "todo"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "todo"
And the "Post replies: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I add a new discussion to "Music history" forum with:
| Subject | Fun instruments |
| Message | I like drums |
And I am on the "Music history" "forum activity" page
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "todo"
And the "Post replies: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I reply "Fun instruments" post from "Music history" forum with:
| Subject | Reply 1 to Fun instruments |
| Message | Guitar is also Fun |
And I am on the "Music history" "forum activity" page
And the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "done"
And the "Post replies: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
# Grade the student
And I am on the "Music history" "forum activity" page logged in as teacher1
And I press "Grade users"
And I set the field "grade" to "60"
And I press "Save"
And I press "Close grader"
And I log out
# All conditions should now be completed.
When I am on the "Music history" "forum activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Start discussions: 1" completion condition of "Music history" is displayed as "done"
And the "Make forum posts: 2" completion condition of "Music history" is displayed as "done"
And the "Post replies: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"

View File

@ -84,6 +84,7 @@ class custom_completion extends activity_custom_completion {
'completionview',
'completionentries',
'completionusegrade',
'completionpassgrade',
];
}
}

View File

@ -0,0 +1,118 @@
@mod @mod_glossary @core_completion
Feature: Pass grade completion in the glossary activity
In order to have visibility of glossary completion requirements
As a student
I need to be able to view my glossary completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And the following "activity" exists:
| activity | glossary |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
And I am on "Course 1" course homepage
And I follow "Music history"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Aggregate type | Average of ratings |
| scale[modgrade_type] | Point |
| scale[modgrade_point] | 100 |
| Ratings > Grade to pass | 50 |
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
| Require grade | 1 |
| completionpassgrade | 1 |
| completionentriesenabled | 1 |
| completionentries | 1 |
And I press "Save and display"
And I log out
Scenario: View automatic completion items as a teacher
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Music history"
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Make entries: 1" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
Scenario: View automatic completion items as a failing student
Given I am on the "Music history" "glossary activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
When I am on "Course 1" course homepage
And I follow "Music history"
And I press "Add a new entry"
And I set the following fields to these values:
| Concept | Blast beats |
| Definition | Repeated fast tempo hits combining bass, snare and cymbal |
And I press "Save changes"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I am on the "Music history" "glossary activity" page logged in as teacher1
And I set the field "rating" to "3"
And I press "Rate"
And I log out
When I am on the "Music history" "glossary activity" page logged in as student1
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
Scenario: View automatic completion items as a passing student
Given I am on the "Music history" "glossary activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
When I am on "Course 1" course homepage
And I follow "Music history"
And I press "Add a new entry"
And I set the following fields to these values:
| Concept | Blast beats |
| Definition | Repeated fast tempo hits combining bass, snare and cymbal |
And I press "Save changes"
And the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "Music history"
And I set the field "rating" to "60"
And I press "Rate"
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "Music history"
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Make entries: 1" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"

View File

@ -0,0 +1,84 @@
@mod @mod_h5pactivity @core_h5p @_file_upload @_switch_iframe @javascript @core_completion
Feature: Pass grade activity completion information in the h5p activity
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| student3 | Vinnie | Student3 | student3@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| teacher1 | C1 | editingteacher |
And I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And I am on "Course 1" course homepage with editing mode on
And I add a "H5P" to section "1"
And I set the following fields to these values:
| Name | Music history |
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
| Require grade | 1 |
| completionpassgrade | 1 |
| gradepass | 25 |
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Package file" filemanager
And I click on "Save and display" "button"
And I log out
Scenario: View automatic completion items
# Teacher view.
Given I am on the "Music history" "h5pactivity activity" page logged in as teacher1
# Given I log in as "teacher1"
# And I am on "Course 1" course homepage
# And I follow "Music history"
And "Music history" should have the "View" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
And I log out
# Student view.
When I am on the "Music history" "h5pactivity activity" page logged in as student1
# When I log in as "student1"
# And I am on "Course 1" course homepage
# And I follow "Music history"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And I click on "Check" "button" in the ".h5p-question-buttons" "css_element"
And I reload the page
And I log out
And I am on the "Music history" "h5pactivity activity" page logged in as student2
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And I set the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" to "Brasilia"
And I set the field with xpath "//input[contains(@aria-label,\"Blank input 2 of 4\")]" to "Washington"
And I set the field with xpath "//input[contains(@aria-label,\"Blank input 3 of 4\")]" to "Berlin"
And I set the field with xpath "//input[contains(@aria-label,\"Blank input 4 of 4\")]" to "Canberra"
And I click on "Check" "button" in the ".h5p-question-buttons" "css_element"
And I switch to the main frame
And I reload the page
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And I log out
And I am on the "Music history" "h5pactivity activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
And "Vinnie Student2" user has completed "Music history" activity
And "Vinnie Student3" user has not completed "Music history" activity

View File

@ -103,6 +103,7 @@ class custom_completion extends activity_custom_completion {
'completiontimespent',
'completionendreached',
'completionusegrade',
'completionpassgrade',
];
}
}

View File

@ -0,0 +1,105 @@
@mod @mod_lesson @core_completion
Feature: Pass grade activity completion in the lesson activity
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| student3 | Vinnie | Student3 | student3@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And the following "activity" exists:
| activity | lesson |
| course | C1 |
| idnumber | mh1 |
| name | Music history |
| section | 1 |
| gradepass | 50 |
| completion | 2 |
| completionview | 1 |
| completionusegrade | 1 |
| completionpassgrade | 1 |
| completionendreached | 1 |
| completiontimespentenabled | 1 |
| completiontimespent | 1 |
And I am on "Course 1" course homepage
And I follow "Music history"
And I follow "Add a question page"
And I set the field "Select a question type" to "Numerical"
And I press "Add a question page"
And I set the following fields to these values:
| Page title | Numerical question |
| Page contents | What is 1 + 2? |
| id_answer_editor_0 | 3 |
| id_jumpto_0 | End of lesson |
| id_enableotheranswers | 1 |
| id_jumpto_6 | Next page |
And I press "Save page"
And I log out
Scenario: View automatic completion items as a teacher
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Music history"
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Spend at least 1 sec on this activity" completion condition
And "Music history" should have the "Go through the activity to the end" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
Scenario: View automatic completion items as a student
Given I am on the "Music history" "lesson activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Spend at least 1 sec on this activity" completion condition of "Music history" is displayed as "todo"
And the "Go through the activity to the end" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
When I am on "Course 1" course homepage
And I follow "Music history"
And I wait "2" seconds
And I reload the page
And the "View" completion condition of "Music history" is displayed as "done"
And the "Spend at least 1 sec on this activity" completion condition of "Music history" is displayed as "done"
And the "Go through the activity to the end" completion condition of "Music history" is displayed as "todo"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I set the field "Your answer" to "3"
And I press "Submit"
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Spend at least 1 sec on this activity" completion condition of "Music history" is displayed as "done"
And the "Go through the activity to the end" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And I log out
And I am on the "Music history" "lesson activity" page logged in as student2
And I wait "2" seconds
And I set the field "Your answer" to "0"
And I press "Submit"
Then the "View" completion condition of "Music history" is displayed as "done"
And the "Spend at least 1 sec on this activity" completion condition of "Music history" is displayed as "done"
And the "Go through the activity to the end" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
And "Vinnie Student2" user has completed "Music history" activity
And "Vinnie Student3" user has not completed "Music history" activity

View File

@ -0,0 +1,84 @@
@mod @mod_lti @core_completion @javascript
Feature: Pass grade activity completion information in the LTI activity
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| student3 | Vinnie | Student3 | student3@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | course | idnumber |
| lti | Music history | C1 | lti1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Enable completion tracking | Yes |
| Show activity completion conditions | Yes |
And I press "Save and display"
And I turn editing mode on
When I open "Music history" actions menu
And I click on "Edit settings" "link" in the "Music history" activity
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
| Require view | 1 |
| Require grade | 1 |
| gradepass | 50 |
| completionpassgrade | 1 |
And I press "Save and return to course"
And I log out
Scenario: View automatic completion items as a teacher
Given I log in as "teacher1"
And I am on "Course 1" course homepage
When I follow "Music history"
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
Scenario: View automatic completion items as a student
Given I am on the "Music history" "lti activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "View > Grader report" in the course gradebook
And I turn editing mode on
And I give the grade "90.00" to the user "Vinnie Student1" for the grade item "Music history"
And I give the grade "20.00" to the user "Vinnie Student2" for the grade item "Music history"
And I press "Save changes"
And I log out
When I am on the "Music history" "lti activity" page logged in as student1
Then the "Receive a grade" completion condition of "Music history" is displayed as "done"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And the "View" completion condition of "Music history" is displayed as "done"
And I log out
When I am on the "Music history" "lti activity" page logged in as student2
Then the "Receive a grade" completion condition of "Music history" is displayed as "done"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And the "View" completion condition of "Music history" is displayed as "done"
And I log out
When I am on the "Music history" "lti activity" page logged in as student3
Then the "Receive a grade" completion condition of "Music history" is displayed as "todo"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the "View" completion condition of "Music history" is displayed as "done"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And "Vinnie Student1" user has completed "Music history" activity
And "Vinnie Student2" user has completed "Music history" activity
And "Vinnie Student3" user has not completed "Music history" activity

View File

@ -49,7 +49,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
'questionsperpage', 'navmethod', 'shuffleanswers',
'sumgrades', 'grade', 'timecreated',
'timemodified', 'password', 'subnet', 'browsersecurity',
'delay1', 'delay2', 'showuserpicture', 'showblocks', 'completionattemptsexhausted', 'completionpass',
'delay1', 'delay2', 'showuserpicture', 'showblocks', 'completionattemptsexhausted',
'completionminattempts', 'allowofflineattempts'));
// Define elements for access rule subplugin settings.

View File

@ -292,6 +292,12 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
$DB->insert_record('quizaccess_seb_quizsettings', $sebsettings);
}
// If we are dealing with a backup from < 4.0 then we need to move completionpass to core.
if (!empty($data->completionpass)) {
$params = ['id' => $this->task->get_moduleid()];
$DB->set_field('course_modules', 'completionpassgrade', $data->completionpass, $params);
}
}
protected function process_quiz_question_instance($data) {

View File

@ -48,23 +48,14 @@ class custom_completion extends activity_custom_completion {
$completionpassorattempts = $this->cm->customdata['customcompletionrules']['completionpassorattemptsexhausted'];
if (empty($completionpassorattempts['completionpass'])) {
if (empty($completionpassorattempts['completionpassgrade'])) {
return true;
}
// Check for passing grade.
$item = grade_item::fetch([
'courseid' => $this->cm->get_course()->id,
'itemtype' => 'mod',
'itemmodule' => 'quiz',
'iteminstance' => $this->cm->instance,
'outcomeid' => null
]);
if ($item) {
$grades = grade_grade::fetch_users_grades($item, [$this->userid], false);
if (!empty($grades[$this->userid]) && $grades[$this->userid]->is_passed($item)) {
return true;
}
if ($this->completionstate &&
isset($this->completionstate['passgrade']) &&
$this->completionstate['passgrade'] == COMPLETION_COMPLETE_PASS) {
return true;
}
// If a passing grade is required and exhausting all available attempts is not accepted for completion,
@ -146,18 +137,15 @@ class custom_completion extends activity_custom_completion {
*/
public function get_custom_rule_descriptions(): array {
$minattempts = $this->cm->customdata['customcompletionrules']['completionminattempts'] ?? 0;
$description['completionminattempts'] = get_string('completiondetail:minattempts', 'mod_quiz', $minattempts);
// Completion pass grade is now part of core. Only show the following if it's combined with min attempts.
$completionpassorattempts = $this->cm->customdata['customcompletionrules']['completionpassorattemptsexhausted'] ?? [];
if (!empty($completionpassorattempts['completionattemptsexhausted'])) {
$passorallattemptslabel = get_string('completiondetail:passorexhaust', 'mod_quiz');
} else {
$passorallattemptslabel = get_string('completiondetail:passgrade', 'mod_quiz');
$description['completionpassorattemptsexhausted'] = get_string('completiondetail:passorexhaust', 'mod_quiz');
}
return [
'completionpassorattemptsexhausted' => $passorallattemptslabel,
'completionminattempts' => get_string('completiondetail:minattempts', 'mod_quiz', $minattempts),
];
return $description;
}
/**
@ -170,6 +158,7 @@ class custom_completion extends activity_custom_completion {
'completionview',
'completionminattempts',
'completionusegrade',
'completionpassgrade',
'completionpassorattemptsexhausted',
];
}

View File

@ -131,9 +131,12 @@ class mod_quiz_external extends external_api {
'reviewspecificfeedback', 'reviewgeneralfeedback', 'reviewrightanswer',
'reviewoverallfeedback', 'questionsperpage', 'navmethod',
'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks',
'completionattemptsexhausted', 'completionpass', 'overduehandling',
'completionattemptsexhausted', 'overduehandling',
'graceperiod', 'canredoquestions', 'allowofflineattempts');
$viewablefields = array_merge($viewablefields, $additionalfields);
// Any course module fields that previously existed in quiz.
$quizdetails['completionpass'] = $quizobj->get_cm()->completionpassgrade;
}
// Fields only for managers.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/quiz/db" VERSION="20200615" COMMENT="XMLDB file for Moodle mod/quiz"
<XMLDB PATH="mod/quiz/db" VERSION="20200630" COMMENT="XMLDB file for Moodle mod/quiz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -45,7 +45,6 @@
<FIELD NAME="showuserpicture" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Option to show the user's picture during the attempt and on the review page."/>
<FIELD NAME="showblocks" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether blocks should be shown on the attempt.php and review.php pages."/>
<FIELD NAME="completionattemptsexhausted" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionpass" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionminattempts" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="allowofflineattempts" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Whether to allow the quiz to be attempted offline in the mobile app"/>
</FIELDS>

View File

@ -60,5 +60,45 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2020061501, 'quiz');
}
if ($oldversion < 2021052503) {
$table = new xmldb_table('quiz');
$field = new xmldb_field('completionpass');
if ($dbman->field_exists($table, $field)) {
$sql = "SELECT q.id " .
"FROM {quiz} q " .
"INNER JOIN {course_modules} cm ON cm.instance = q.id " .
"INNER JOIN {modules} m ON m.id = cm.module " .
"WHERE m.name = :name AND q.completionpass = :completionpass";
/** @var moodle_recordset $records */
$records = $DB->get_recordset_sql($sql, ['name' => 'quiz', 'completionpass' => 1], 0, 1000);
while ($records->valid()) {
foreach ($records as $record) {
$ids[] = $record->id;
}
if ($ids) {
list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
$DB->set_field_select('course_modules', 'completionpassgrade', 1, "instance $insql", $params);
// Reset the value so it doesn't get picked on the next run. The field will be dropped later.
$DB->set_field_select('quiz', 'completionpass', 0, "id $insql", $params);
// Get the next batch of records.
$records = $DB->get_recordset_sql($sql, ['name' => 'quiz', 'completionpass' => 1], 0, 1000);
}
}
$records->close();
// We have completed our checks. Drop the field.
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
}
upgrade_mod_savepoint(true, 2021052503, 'quiz');
}
return true;
}

View File

@ -40,7 +40,7 @@ function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $user
debugging('quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', DEBUG_DEVELOPER);
if (!$quiz->completionpass) {
if (!$cm->completionpassgrade) {
return true;
}
@ -118,7 +118,7 @@ function quiz_get_completion_state($course, $cm, $userid, $type) {
// No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
$quiz = $DB->get_record('quiz', array('id' => $cm->instance), '*', MUST_EXIST);
if (!$quiz->completionattemptsexhausted && !$quiz->completionpass && !$quiz->completionminattempts) {
if (!$quiz->completionattemptsexhausted && !$cm->completionpassgrade && !$quiz->completionminattempts) {
return $type;
}

View File

@ -2,3 +2,8 @@ numattemptsmade,mod_quiz
reviewofattempt,mod_quiz
reviewofpreview,mod_quiz
settingsoverrides,mod_quiz
completionpass,mod_quiz
completionpassdesc,mod_quiz
completionpass_help,mod_quiz
completiondetail:passgrade,mod_quiz
gradetopassnotset,mod_quiz

View File

@ -181,15 +181,11 @@ $string['commentorgrade'] = 'Make comment or override grade';
$string['comments'] = 'Comments';
$string['completedon'] = 'Completed on';
$string['completiondetail:minattempts'] = 'Make attempts: {$a}';
$string['completiondetail:passgrade'] = 'Receive a pass grade';
$string['completiondetail:passorexhaust'] = 'Receive a pass grade or complete all available attempts';
$string['completionminattempts'] = 'Minimum number of attempts:';
$string['completionminattemptsdesc'] = 'Minimum number of attempts required: {$a}';
$string['completionminattemptsgroup'] = 'Require attempts';
$string['completionminattemptserror'] = 'Minimum number of attempts must be lower or equal to attempts allowed.';
$string['completionpass'] = 'Require passing grade';
$string['completionpassdesc'] = 'Student must achieve a passing grade to complete this activity';
$string['completionpass_help'] = 'If enabled, this activity is considered complete when the student receives a pass grade (as specified in the Grade section of the quiz settings) or higher.';
$string['completionpassorattemptsexhausteddesc'] = 'Student must achieve a passing grade, or exhaust all available attempts to complete this activity';
$string['completionattemptsexhausted'] = 'Or all available attempts completed';
$string['completionattemptsexhausted_help'] = 'Mark quiz complete when the student has exhausted the maximum number of attempts.';
@ -421,7 +417,6 @@ $string['grademethod_help'] = 'When multiple attempts are allowed, the following
* Last attempt (all other attempts are ignored)';
$string['gradesdeleted'] = 'Quiz grades deleted';
$string['gradesofar'] = '{$a->method}: {$a->mygrade} / {$a->quizgrade}.';
$string['gradetopassnotset'] = 'This quiz does not yet have a grade to pass set. It may be set in the Grade section of the quiz settings.';
$string['gradetopassmustbeset'] = 'Grade to pass cannot be zero as this quiz has its completion method set to require passing grade. Please set a non-zero value.';
$string['gradetopassoutof'] = 'Grade to pass: {$a->grade} out of {$a->maxgrade}';
$string['gradingdetails'] = 'Marks for this submission: {$a->raw}/{$a->max}.';
@ -1037,3 +1032,10 @@ $string['settingsoverrides'] = 'Settings overrides';
// Deprecated since Moodle 3.11.
$string['completionattemptsexhausteddesc'] = 'Complete if all available attempts are exhausted';
// Deprecated since Moodle 4.0.
$string['completionpass'] = 'Require passing grade';
$string['completionpassdesc'] = 'Student must achieve a passing grade to complete this activity';
$string['completionpass_help'] = 'If enabled, this activity is considered complete when the student receives a pass grade (as specified in the Grade section of the quiz settings) or higher.';
$string['completiondetail:passgrade'] = 'Receive a pass grade';
$string['gradetopassnotset'] = 'This quiz does not yet have a grade to pass set. It may be set in the Grade section of the quiz settings.';

View File

@ -1129,9 +1129,9 @@ function quiz_process_options($quiz) {
// Ensure that disabled checkboxes in completion settings are set to 0.
if (empty($quiz->completionusegrade)) {
$quiz->completionpass = 0;
$quiz->completionpassgrade = 0;
}
if (empty($quiz->completionpass)) {
if (empty($quiz->completionpassgrade)) {
$quiz->completionattemptsexhausted = 0;
}
if (empty($quiz->completionminattemptsenabled)) {
@ -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.
*
@ -2092,7 +2101,7 @@ function quiz_get_coursemodule_info($coursemodule) {
global $DB;
$dbparams = ['id' => $coursemodule->instance];
$fields = 'id, name, intro, introformat, completionattemptsexhausted, completionpass, completionminattempts,
$fields = 'id, name, intro, introformat, completionattemptsexhausted, completionminattempts,
timeopen, timeclose';
if (!$quiz = $DB->get_record('quiz', $dbparams, $fields)) {
return false;
@ -2108,9 +2117,9 @@ function quiz_get_coursemodule_info($coursemodule) {
// Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
if ($quiz->completionpass || $quiz->completionattemptsexhausted) {
if ($quiz->completionattemptsexhausted) {
$result->customdata['customcompletionrules']['completionpassorattemptsexhausted'] = [
'completionpass' => $quiz->completionpass,
'completionpassgrade' => $coursemodule->completionpassgrade,
'completionattemptsexhausted' => $quiz->completionattemptsexhausted,
];
} else {
@ -2206,16 +2215,11 @@ function mod_quiz_get_completion_active_rule_descriptions($cm) {
if (!empty($rules['completionpassorattemptsexhausted'])) {
if (!empty($rules['completionpassorattemptsexhausted']['completionattemptsexhausted'])) {
$descriptions[] = get_string('completionpassorattemptsexhausteddesc', 'quiz');
} else if (!empty($rules['completionpassorattemptsexhausted']['completionpass'])) {
$descriptions[] = get_string('completionpassdesc', 'quiz',
format_time($rules['completionpassorattemptsexhausted']['completionpass']));
}
} else {
// Fallback.
if (!empty($rules['completionattemptsexhausted'])) {
$descriptions[] = get_string('completionpassorattemptsexhausteddesc', 'quiz');
} else if (!empty($rules['completionpass'])) {
$descriptions[] = get_string('completionpassdesc', 'quiz', format_time($rules['completionpass']));
}
}

View File

@ -1877,7 +1877,7 @@ function quiz_attempt_submitted_handler($event) {
// Update completion state.
$completion = new completion_info($course);
if ($completion->is_enabled($cm) &&
($quiz->completionattemptsexhausted || $quiz->completionpass || $quiz->completionminattempts)) {
($quiz->completionattemptsexhausted || $quiz->completionminattempts)) {
$completion->update_state($cm, COMPLETION_COMPLETE, $event->userid);
}
return quiz_send_notification_messages($course, $quiz, $attempt,

View File

@ -527,19 +527,6 @@ class mod_quiz_mod_form extends moodleform_mod {
}
}
if (array_key_exists('completion', $data) && $data['completion'] == COMPLETION_TRACKING_AUTOMATIC) {
$completionpass = isset($data['completionpass']) ? $data['completionpass'] : $this->current->completionpass;
// Show an error if require passing grade was selected and the grade to pass was set to 0.
if ($completionpass && (empty($data['gradepass']) || grade_floatval($data['gradepass']) == 0)) {
if (isset($data['completionpass'])) {
$errors['completionpassgroup'] = get_string('gradetopassnotset', 'quiz');
} else {
$errors['gradepass'] = get_string('gradetopassmustbeset', 'quiz');
}
}
}
if (!empty($data['completionminattempts'])) {
if ($data['attempts'] > 0 && $data['completionminattempts'] > $data['attempts']) {
$errors['completionminattemptsgroup'] = get_string('completionminattemptserror', 'quiz');
@ -615,17 +602,11 @@ class mod_quiz_mod_form extends moodleform_mod {
$mform = $this->_form;
$items = array();
$group = array();
$group[] = $mform->createElement('advcheckbox', 'completionpass', null, get_string('completionpass', 'quiz'),
array('group' => 'cpass'));
$mform->disabledIf('completionpass', 'completionusegrade', 'notchecked');
$group[] = $mform->createElement('advcheckbox', 'completionattemptsexhausted', null,
get_string('completionattemptsexhausted', 'quiz'),
array('group' => 'cattempts'));
$mform->disabledIf('completionattemptsexhausted', 'completionpass', 'notchecked');
$mform->addGroup($group, 'completionpassgroup', get_string('completionpass', 'quiz'), ' &nbsp; ', false);
$mform->addHelpButton('completionpassgroup', 'completionpass', 'quiz');
$items[] = 'completionpassgroup';
$mform->addElement('advcheckbox', 'completionattemptsexhausted', null,
get_string('completionattemptsexhausted', 'quiz'),
array('group' => 'cattempts'));
$mform->disabledIf('completionattemptsexhausted', 'completionpassgrade', 'notchecked');
$items[] = 'completionattemptsexhausted';
$group = array();
$group[] = $mform->createElement('checkbox', 'completionminattemptsenabled', '',
@ -648,7 +629,6 @@ class mod_quiz_mod_form extends moodleform_mod {
*/
public function completion_rule_enabled($data) {
return !empty($data['completionattemptsexhausted']) ||
!empty($data['completionpass']) ||
!empty($data['completionminattemptsenabled']);
}

View File

@ -25,8 +25,8 @@ Feature: Set a quiz to be marked complete when the student uses all attempts all
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpass | completionattemptsexhausted |
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpassgrade | completionattemptsexhausted |
| quiz | Test quiz name | C1 | quiz1 | 2 | 5.00 | 2 | 1 | 1 | 1 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
@ -37,7 +37,8 @@ Feature: Set a quiz to be marked complete when the student uses all attempts all
Scenario: student1 uses up both attempts without passing
When I log in as "student1"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "failed"
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"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "todo"
And I follow "Test quiz name"
And I press "Re-attempt quiz"
@ -45,10 +46,12 @@ Feature: Set a quiz to be marked complete when the student uses all attempts all
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I am on "Course 1" course homepage
Then the "Receive a grade" completion condition of "Test quiz name" is displayed as "failed"
Then 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"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "done"
And I follow "Test quiz name"
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "failed"
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"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "done"
And I log out
And I log in as "teacher1"

View File

@ -25,8 +25,8 @@ Feature: Set a quiz to be marked complete when the student passes
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpass | completionview |
| quiz | Test quiz name | C1 | quiz1 | 4 | 5.00 | 2 | 1 | 1 | 1 |
| activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpassgrade | completionview |
| quiz | Test quiz name | C1 | quiz1 | 4 | 5.00 | 2 | 1 | 1 | 1 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
@ -35,18 +35,18 @@ Feature: Set a quiz to be marked complete when the student passes
When I log in as "student1"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a pass grade" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "todo"
And the "View" completion condition of "Test quiz name" is displayed as "todo"
And user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | True |
And I follow "Test quiz name"
Then the "Receive a grade" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a pass 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 "done"
And the "View" completion condition of "Test quiz name" is displayed as "done"
And I am on "Course 1" course homepage
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a pass 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 "done"
And the "View" completion condition of "Test quiz name" is displayed as "done"
And I log out
And I log in as "teacher1"

View File

@ -35,7 +35,7 @@ Feature: View activity completion in the quiz activity
| completion | 2 |
| completionview | 1 |
| completionusegrade | 1 |
| completionpass | 1 |
| completionpassgrade | 1 |
| completionattemptsexhausted | 1 |
| completionminattemptsenabled | 1 |
| completionminattempts | 1 |
@ -50,6 +50,7 @@ Feature: View activity completion in the quiz activity
And the "View" completion condition of "Test quiz name" is displayed as "done"
And the "Make attempts: 1" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "todo"
And user "student1" has attempted "Test quiz name" with responses:
| slot | response |
@ -58,7 +59,8 @@ Feature: View activity completion in the quiz activity
And I follow "Test quiz name"
And the "View" completion condition of "Test quiz name" is displayed as "done"
And the "Make attempts: 1" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "failed"
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"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "todo"
And I press "Re-attempt quiz"
And I set the field "True" to "1"
@ -68,4 +70,5 @@ Feature: View activity completion in the quiz activity
And the "View" completion condition of "Test quiz name" is displayed as "done"
And the "Make attempts: 1" completion condition of "Test quiz name" is displayed as "done"
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 "done"
And the "Receive a pass grade or complete all available attempts" completion condition of "Test quiz name" is displayed as "done"

View File

@ -20,6 +20,7 @@ namespace mod_quiz;
use advanced_testcase;
use cm_info;
use core_completion\cm_completion_details;
use grade_item;
use mod_quiz\completion\custom_completion;
use question_engine;
@ -127,11 +128,8 @@ class custom_completion_test extends advanced_testcase {
}
/**
* Test checking the completion state of a quiz.
* Test checking the completion state of a quiz base on core's completionpassgrade criteria.
* The quiz requires a passing grade to be completed.
*
* @covers ::get_state
* @covers ::get_custom_rule_descriptions
*/
public function test_completionpass() {
list($students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
@ -139,7 +137,7 @@ class custom_completion_test extends advanced_testcase {
'qtype' => 'numerical',
'quizoptions' => [
'completionusegrade' => 1,
'completionpass' => 1
'completionpassgrade' => 1
]
]);
@ -153,13 +151,14 @@ class custom_completion_test extends advanced_testcase {
'tosubmit' => [1 => ['answer' => '3.14']]
]);
$completioninfo = new \completion_info($cm->get_course());
$completiondetails = new cm_completion_details($completioninfo, $cm, (int) $passstudent->id);
// Check the results.
$customcompletion = new custom_completion($cm, (int) $passstudent->id);
$this->assertArrayHasKey('completionpassorattemptsexhausted', $cm->customdata['customcompletionrules']);
$this->assertEquals(COMPLETION_COMPLETE, $customcompletion->get_state('completionpassorattemptsexhausted'));
$this->assertEquals(COMPLETION_COMPLETE_PASS, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a pass grade',
$customcompletion->get_custom_rule_descriptions()['completionpassorattemptsexhausted']
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Do a failing attempt.
@ -170,13 +169,13 @@ class custom_completion_test extends advanced_testcase {
'tosubmit' => [1 => ['answer' => '0']]
]);
$completiondetails = new cm_completion_details($completioninfo, $cm, (int) $failstudent->id);
// Check the results.
$customcompletion = new custom_completion($cm, (int) $failstudent->id);
$this->assertArrayHasKey('completionpassorattemptsexhausted', $cm->customdata['customcompletionrules']);
$this->assertEquals(COMPLETION_INCOMPLETE, $customcompletion->get_state('completionpassorattemptsexhausted'));
$this->assertEquals(COMPLETION_COMPLETE_FAIL, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a pass grade',
$customcompletion->get_custom_rule_descriptions()['completionpassorattemptsexhausted']
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
}
@ -194,7 +193,7 @@ class custom_completion_test extends advanced_testcase {
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpass' => 1,
'completionpassgrade' => 1,
'completionattemptsexhausted' => 1
]
]);
@ -209,8 +208,11 @@ class custom_completion_test extends advanced_testcase {
'tosubmit' => [1 => ['answer' => '3.14']]
]);
$completioninfo = new \completion_info($cm->get_course());
// Check the results. Quiz is completed by $passstudent because of passing grade.
$customcompletion = new custom_completion($cm, (int) $passstudent->id);
$studentid = (int) $passstudent->id;
$customcompletion = new custom_completion($cm, $studentid, $completioninfo->get_core_completion_state($cm, $studentid));
$this->assertArrayHasKey('completionpassorattemptsexhausted', $cm->customdata['customcompletionrules']);
$this->assertEquals(COMPLETION_COMPLETE, $customcompletion->get_state('completionpassorattemptsexhausted'));
$this->assertEquals(
@ -227,7 +229,8 @@ class custom_completion_test extends advanced_testcase {
]);
// Check the results. Quiz is not completed by $exhauststudent yet because of failing grade and of remaining attempts.
$customcompletion = new custom_completion($cm, (int) $exhauststudent->id);
$studentid = (int) $exhauststudent->id;
$customcompletion = new custom_completion($cm, $studentid, $completioninfo->get_core_completion_state($cm, $studentid));
$this->assertArrayHasKey('completionpassorattemptsexhausted', $cm->customdata['customcompletionrules']);
$this->assertEquals(COMPLETION_INCOMPLETE, $customcompletion->get_state('completionpassorattemptsexhausted'));
$this->assertEquals(
@ -244,7 +247,7 @@ class custom_completion_test extends advanced_testcase {
]);
// Check the results. Quiz is completed by $exhauststudent because there are no remaining attempts.
$customcompletion = new custom_completion($cm, (int) $exhauststudent->id);
$customcompletion = new custom_completion($cm, $studentid, $completioninfo->get_core_completion_state($cm, $studentid));
$this->assertArrayHasKey('completionpassorattemptsexhausted', $cm->customdata['customcompletionrules']);
$this->assertEquals(COMPLETION_COMPLETE, $customcompletion->get_state('completionpassorattemptsexhausted'));
$this->assertEquals(

View File

@ -228,6 +228,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$quiz1->groupingid = 0;
$quiz1->hasquestions = 0;
$quiz1->hasfeedback = 0;
$quiz1->completionpass = 0;
$quiz1->autosaveperiod = get_config('quiz', 'autosaveperiod');
$quiz1->introfiles = [];
@ -239,6 +240,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$quiz2->groupingid = 0;
$quiz2->hasquestions = 0;
$quiz2->hasfeedback = 0;
$quiz2->completionpass = 0;
$quiz2->autosaveperiod = get_config('quiz', 'autosaveperiod');
$quiz2->introfiles = [];

View File

@ -226,7 +226,7 @@ class mod_quiz_lib_testcase extends advanced_testcase {
'qtype' => 'numerical',
'quizoptions' => [
'completionusegrade' => 1,
'completionpass' => 1
'completionpassgrade' => 1
]
]);
@ -273,7 +273,7 @@ class mod_quiz_lib_testcase extends advanced_testcase {
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpass' => 1,
'completionpassgrade' => 1,
'completionattemptsexhausted' => 1
]
]);
@ -383,7 +383,7 @@ class mod_quiz_lib_testcase extends advanced_testcase {
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpass' => 1,
'completionpassgrade' => 1,
'completionminattemptsenabled' => 1,
'completionminattempts' => 2
]
@ -1140,8 +1140,8 @@ class mod_quiz_lib_testcase extends advanced_testcase {
'course' => $course->id,
'completion' => 2,
'completionusegrade' => 1,
'completionpassgrade' => 1,
'completionattemptsexhausted' => 1,
'completionpass' => 1
]);
$quiz2 = $this->getDataGenerator()->create_module('quiz', [
'course' => $course->id,
@ -1157,7 +1157,6 @@ class mod_quiz_lib_testcase extends advanced_testcase {
$moddefaults = new stdClass();
$moddefaults->customdata = ['customcompletionrules' => [
'completionattemptsexhausted' => 1,
'completionpass' => 1
]];
$moddefaults->completion = 2;

View File

@ -6,6 +6,8 @@ This files describes API changes in the quiz code.
- process_finish() in mod/quiz/attemptlib.php
- quiz_send_confirmation() in mod/quiz/locallib.php
- quiz_send_notification_messages() in mod/quiz/locallib.php
* The completionpass criteria has been moved to core as 'completionpassgrade'. Refer to completion/upgrade.txt for
further information.
=== 3.11 ===

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2021052502;
$plugin->version = 2021052503;
$plugin->requires = 2021052500;
$plugin->component = 'mod_quiz';

View File

@ -210,6 +210,7 @@ class custom_completion extends activity_custom_completion {
'completionstatusallscos',
'completionstatusrequired',
'completionusegrade',
'completionpassgrade',
'completionscorerequired',
];
}

View File

@ -275,6 +275,9 @@ class mod_scorm_mod_form extends moodleform_mod {
$this->standard_coursemodule_elements();
// A SCORM module should define this within itself and is not needed here.
$mform->removeElement('completionpassgrade');
// Buttons.
$this->add_action_buttons();
}

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.

View File

@ -32,6 +32,7 @@ require_once($CFG->dirroot . '/course/moodleform_mod.php');
require_once(__DIR__ . '/locallib.php');
require_once($CFG->libdir . '/filelib.php');
use core_grades\component_gradeitems;
/**
* Module settings form for Workshop instances
*/
@ -462,6 +463,23 @@ class mod_workshop_mod_form extends moodleform_mod {
}
}
// We need to do a custom completion validation because workshop grade items identifiers divert from standard.
// Refer to validation defined in moodleform_mod.php.
if (isset($data['completionpassgrade']) && $data['completionpassgrade'] &&
isset($data['completiongradeitemnumber'])) {
$itemnames = component_gradeitems::get_itemname_mapping_for_component('mod_workshop');
$gradepassfield = $itemnames[(int) $data['completiongradeitemnumber']] . 'gradepass';
if (!isset($data[$gradepassfield]) || grade_floatval($data[$gradepassfield]) == 0) {
$errors['completionpassgrade'] = get_string(
'activitygradetopassnotset',
'completion'
);
} else {
// We have validated grade pass. Unset any errors.
unset($errors['completionpassgrade']);
}
}
if (!$data['submissiontypetextavailable'] && !$data['submissiontypefileavailable']) {
// One submission type must be available.
$errors['submissiontypes'] = get_string('nosubmissiontype', 'workshop');

View File

@ -0,0 +1,145 @@
@mod @mod_workshop @core_completion @javascript
Feature: Workshop submission and assessment with pass grade activity completion
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Sam1 | Student1 | student1@example.com |
| student2 | Sam2 | Student2 | student2@example.com |
| student3 | Sam3 | Student3 | student3@example.com |
| student4 | Sam4 | Student4 | student3@example.com |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | enablecompletion |
| Course1 | c1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | c1 | student |
| student2 | c1 | student |
| student3 | c1 | student |
| student4 | c1 | student |
| teacher1 | c1 | editingteacher |
And the following "activity" exists:
| activity | workshop |
| name | TestWorkshop |
| intro | Test workshop description |
| course | c1 |
| idnumber | workshop1 |
| submissiontypetext | 2 |
| submissiontypefile | 1 |
| completion | 2 |
| completiongradeitemnumber | 0 |
| submissiongradepass | 40 |
| gradinggradepass | 60 |
| completionpassgrade | 1 |
# teacher1 sets up assessment form and changes the phase to submission
When I am on the "TestWorkshop" "workshop activity" page logged in as teacher1
And I edit assessment form in workshop "TestWorkshop" as:"
| id_description__idx_0_editor | Aspect1 |
| id_description__idx_1_editor | Aspect2 |
| id_description__idx_2_editor | |
And I change phase in workshop "TestWorkshop" to "Submission phase"
And I log out
# student1 submits
And I am on the "TestWorkshop" "workshop activity" page logged in as student1
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission1 |
| Submission content | Some content |
And I log out
# student2 submits
And I am on the "TestWorkshop" "workshop activity" page logged in as student2
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission2 |
| Submission content | Some content |
And I log out
# student3 submits
And I am on the "TestWorkshop" "workshop activity" page logged in as student3
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission3 |
| Submission content | Some content |
And I log out
# teacher1 allocates reviewers and changes the phase to assessment
And I am on the "TestWorkshop" "workshop activity" page logged in as teacher1
And I allocate submissions in workshop "TestWorkshop" as:"
| Participant | Reviewer |
| Sam1 Student1 | Sam2 Student2 |
| Sam2 Student2 | Sam1 Student1 |
| Sam3 Student3 | Sam1 Student1 |
| Sam2 Student2 | Sam4 Student4 |
And I follow "TestWorkshop"
And I change phase in workshop "TestWorkshop" to "Assessment phase"
And I log out
# student1 assesses work of student2 and student3
And I am on the "TestWorkshop" "workshop activity" page logged in as student1
And I assess submission "Sam2" in workshop "TestWorkshop" as:"
| grade__idx_0 | 5 / 10 |
| peercomment__idx_0 | You can do better |
| grade__idx_1 | 10 / 10 |
| peercomment__idx_1 | Amazing |
| Feedback for the author | Good work |
And I am on "Course1" course homepage
And I assess submission "Sam3" in workshop "TestWorkshop" as:"
| grade__idx_0 | 9 / 10 |
| peercomment__idx_0 | Well done |
| grade__idx_1 | 8 / 10 |
| peercomment__idx_1 | Very good |
| Feedback for the author | No comments |
And I log out
# student2 assesses work of student1
And I am on the "TestWorkshop" "workshop activity" page logged in as student2
And I assess submission "Sam1" in workshop "TestWorkshop" as:"
| grade__idx_0 | 6 / 10 |
| peercomment__idx_0 | |
| grade__idx_1 | 7 / 10 |
| peercomment__idx_1 | |
| Feedback for the author | Keep it up |
And I log out
# teacher1 makes sure he can see all peer grades
And I am on the "TestWorkshop" "workshop activity" page logged in as teacher1
And I click on "//table/tbody/tr[td[contains(concat(' ', normalize-space(@class), ' '), ' participant ') and contains(.,'Sam2')]]/td[contains(concat(' ', normalize-space(@class), ' '), ' receivedgrade ') and contains(.,'Sam1')]/descendant::a[@class='grade']" "xpath_element"
# teacher1 assesses the work on submission1 and assesses the assessment of peer
And I set the following fields to these values:
| Override grade for assessment | 11 |
| Feedback for the reviewer | |
And I press "Save and close"
And I change phase in workshop "TestWorkshop" to "Grading evaluation phase"
And I follow "Submission1"
And I press "Assess"
And I set the following fields to these values:
| grade__idx_0 | 1 / 10 |
| peercomment__idx_0 | Extremely bad |
| grade__idx_1 | 2 / 10 |
| peercomment__idx_1 | Very bad |
| Feedback for the author | Your peers overestimate you |
And I press "Save and close"
And I press "Re-calculate grades"
And I change phase in workshop "TestWorkshop" to "Closed"
And I log out
Scenario: Add and assess submissions in workshop with javascript enabled
And I log in as "student1"
And I am on "Course1" course homepage
And the "Receive a grade" completion condition of "TestWorkshop" is displayed as "done"
And the "Receive a passing grade" completion condition of "TestWorkshop" is displayed as "failed"
And I log out
And I log in as "student2"
And I am on "Course1" course homepage
And the "Receive a grade" completion condition of "TestWorkshop" is displayed as "done"
And the "Receive a passing grade" completion condition of "TestWorkshop" is displayed as "done"
And I log out
And I log in as "student3"
And I am on "Course1" course homepage
And the "Receive a grade" completion condition of "TestWorkshop" is displayed as "done"
And the "Receive a passing grade" completion condition of "TestWorkshop" is displayed as "done"
And I log out
And I log in as "student4"
And I am on "Course1" course homepage
And the "Receive a grade" completion condition of "TestWorkshop" is displayed as "todo"
And the "Receive a passing grade" completion condition of "TestWorkshop" is displayed as "todo"
And I log out
And I log in as "teacher1"
And I am on "Course1" course homepage
And "Sam1 Student1" user has completed "TestWorkshop" activity
And "Sam2 Student2" user has completed "TestWorkshop" activity
And "Sam3 Student3" user has completed "TestWorkshop" activity
And "Sam4 Student4" user has not completed "TestWorkshop" activity

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021100600.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021100600.03; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev+ (Build: 20211006)'; // Human-friendly version name