diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index d17b0da0ddb..e507698812c 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -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)); diff --git a/badges/tests/behat/criteria_activity.feature b/badges/tests/behat/criteria_activity.feature index 128e9325b6a..98cae5e8fb7 100644 --- a/badges/tests/behat/criteria_activity.feature +++ b/badges/tests/behat/criteria_activity.feature @@ -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" diff --git a/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature b/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature index 7697f77cd7f..ffd06c43d0e 100644 --- a/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature +++ b/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature @@ -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" diff --git a/completion/classes/activity_custom_completion.php b/completion/classes/activity_custom_completion.php index 4f06cc205ee..b7f95a42136 100644 --- a/completion/classes/activity_custom_completion.php +++ b/completion/classes/activity_custom_completion.php @@ -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; } /** diff --git a/completion/classes/bulkedit_form.php b/completion/classes/bulkedit_form.php index a67cce75f69..0e6159aee3c 100644 --- a/completion/classes/bulkedit_form.php +++ b/completion/classes/bulkedit_form.php @@ -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) { diff --git a/completion/classes/cm_completion_details.php b/completion/classes/cm_completion_details.php index d08196189a4..79004fb7da9 100644 --- a/completion/classes/cm_completion_details.php +++ b/completion/classes/cm_completion_details.php @@ -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) { diff --git a/completion/classes/edit_base_form.php b/completion/classes/edit_base_form.php index 505c7891689..903a51ab00a 100644 --- a/completion/classes/edit_base_form.php +++ b/completion/classes/edit_base_form.php @@ -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'); } diff --git a/completion/classes/manager.php b/completion/classes/manager.php index 2f66775e7f0..83738d113c3 100644 --- a/completion/classes/manager.php +++ b/completion/classes/manager.php @@ -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) { diff --git a/completion/criteria/completion_criteria_activity.php b/completion/criteria/completion_criteria_activity.php index 118db635b31..3b80836636a 100644 --- a/completion/criteria/completion_criteria_activity.php +++ b/completion/criteria/completion_criteria_activity.php @@ -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']); diff --git a/completion/tests/behat/bulk_edit_activity_completion.feature b/completion/tests/behat/bulk_edit_activity_completion.feature index 371998c0780..316ca4654cd 100644 --- a/completion/tests/behat/bulk_edit_activity_completion.feature +++ b/completion/tests/behat/bulk_edit_activity_completion.feature @@ -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" diff --git a/completion/tests/behat/enable_completion_on_pass_grade.feature b/completion/tests/behat/enable_completion_on_pass_grade.feature new file mode 100644 index 00000000000..062bacda203 --- /dev/null +++ b/completion/tests/behat/enable_completion_on_pass_grade.feature @@ -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" diff --git a/completion/tests/behat/enable_completion_on_view_and_grade.feature b/completion/tests/behat/enable_completion_on_view_and_grade.feature new file mode 100644 index 00000000000..cf9d21a63d6 --- /dev/null +++ b/completion/tests/behat/enable_completion_on_view_and_grade.feature @@ -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" diff --git a/completion/tests/bulk_update_test.php b/completion/tests/bulk_update_test.php index 69dcd09129d..834ba7fd5ee 100644 --- a/completion/tests/bulk_update_test.php +++ b/completion/tests/bulk_update_test.php @@ -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]], diff --git a/completion/tests/cm_completion_details_test.php b/completion/tests/cm_completion_details_test.php index bdbfcf683f9..00d660a110d 100644 --- a/completion/tests/cm_completion_details_test.php +++ b/completion/tests/cm_completion_details_test.php @@ -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() { diff --git a/completion/tests/externallib_test.php b/completion/tests/externallib_test.php index 2d5c6e8f729..098efb361d7 100644 --- a/completion/tests/externallib_test.php +++ b/completion/tests/externallib_test.php @@ -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); diff --git a/completion/upgrade.txt b/completion/upgrade.txt index 5cdbea218b3..a84e7c1a082 100644 --- a/completion/upgrade.txt +++ b/completion/upgrade.txt @@ -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: diff --git a/course/externallib.php b/course/externallib.php index 17b5fe644d3..0ae4854ca0c 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -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), diff --git a/course/lib.php b/course/lib.php index b776c3af93c..66c8d35624f 100644 --- a/course/lib.php +++ b/course/lib.php @@ -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; diff --git a/course/modlib.php b/course/modlib.php index 43cc5448788..2afb5963cea 100644 --- a/course/modlib.php +++ b/course/modlib.php @@ -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); diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index c99ca7122e3..f444d47eeca 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -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'); } } diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index e7464d30b17..32f66639e07 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -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); diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index d715d60e2fe..abe3635d0d7 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -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']); diff --git a/course/tests/modlib_test.php b/course/tests/modlib_test.php index c6eab11cc9c..937fcd281fd 100644 --- a/course/tests/modlib_test.php +++ b/course/tests/modlib_test.php @@ -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); diff --git a/lang/en/completion.php b/lang/en/completion.php index 9c60666c087..1f4ec18664e 100644 --- a/lang/en/completion.php +++ b/lang/en/completion.php @@ -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 {$a} 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 {$a} have been hidden. To view unselect other activities'; $string['editcoursecompletionsettings'] = 'Edit course completion settings'; diff --git a/lib/completionlib.php b/lib/completionlib.php index 62701136db0..e9b5007f40b 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -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. diff --git a/lib/db/install.xml b/lib/db/install.xml index eeb1af648de..a35a832e9e7 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -304,6 +304,7 @@ + @@ -3866,6 +3867,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index ceb9dc7b773..2d14a0ea5f6 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -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; } diff --git a/lib/modinfolib.php b/lib/modinfolib.php index 65d2aabe7b8..191e5e062a6 100644 --- a/lib/modinfolib.php +++ b/lib/modinfolib.php @@ -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) diff --git a/lib/testing/generator/module_generator.php b/lib/testing/generator/module_generator.php index 4a2396e552a..bfb543e0af4 100644 --- a/lib/testing/generator/module_generator.php +++ b/lib/testing/generator/module_generator.php @@ -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() diff --git a/lib/testing/tests/generator_test.php b/lib/testing/tests/generator_test.php index ad7c4fee359..e1b2cfa72ae 100644 --- a/lib/testing/tests/generator_test.php +++ b/lib/testing/tests/generator_test.php @@ -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); diff --git a/lib/tests/completionlib_test.php b/lib/tests/completionlib_test.php index 4712b5b5d64..91f3c8340e9 100644 --- a/lib/tests/completionlib_test.php +++ b/lib/tests/completionlib_test.php @@ -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); diff --git a/lib/tests/modinfolib_test.php b/lib/tests/modinfolib_test.php index b58c7582f1d..0f6222871c2 100644 --- a/lib/tests/modinfolib_test.php +++ b/lib/tests/modinfolib_test.php @@ -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); diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 9f5db3e4c91..38f10134294 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -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. diff --git a/mod/assign/classes/completion/custom_completion.php b/mod/assign/classes/completion/custom_completion.php index 79d2d0b2433..ab14b3eab80 100644 --- a/mod/assign/classes/completion/custom_completion.php +++ b/mod/assign/classes/completion/custom_completion.php @@ -89,6 +89,7 @@ class custom_completion extends activity_custom_completion { 'completionview', 'completionsubmit', 'completionusegrade', + 'completionpassgrade', ]; } } diff --git a/mod/data/classes/completion/custom_completion.php b/mod/data/classes/completion/custom_completion.php index 0f00b797762..3e1b29de066 100644 --- a/mod/data/classes/completion/custom_completion.php +++ b/mod/data/classes/completion/custom_completion.php @@ -80,6 +80,7 @@ class custom_completion extends activity_custom_completion { 'completionview', 'completionentries', 'completionusegrade', + 'completionpassgrade', ]; } } diff --git a/mod/data/tests/behat/data_activity_completion_pass_grade.feature b/mod/data/tests/behat/data_activity_completion_pass_grade.feature new file mode 100644 index 00000000000..f9230954f2c --- /dev/null +++ b/mod/data/tests/behat/data_activity_completion_pass_grade.feature @@ -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 diff --git a/mod/forum/classes/completion/custom_completion.php b/mod/forum/classes/completion/custom_completion.php index ffe5b98b904..e6d90367166 100644 --- a/mod/forum/classes/completion/custom_completion.php +++ b/mod/forum/classes/completion/custom_completion.php @@ -112,6 +112,7 @@ class custom_completion extends activity_custom_completion { 'completionreplies', 'completionposts', 'completionusegrade', + 'completionpassgrade', ]; } } diff --git a/mod/forum/tests/behat/forum_activity_completion_pass_grade.feature b/mod/forum/tests/behat/forum_activity_completion_pass_grade.feature new file mode 100644 index 00000000000..db71d7b5beb --- /dev/null +++ b/mod/forum/tests/behat/forum_activity_completion_pass_grade.feature @@ -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" diff --git a/mod/glossary/classes/completion/custom_completion.php b/mod/glossary/classes/completion/custom_completion.php index 41456447213..dcdaa71616c 100644 --- a/mod/glossary/classes/completion/custom_completion.php +++ b/mod/glossary/classes/completion/custom_completion.php @@ -84,6 +84,7 @@ class custom_completion extends activity_custom_completion { 'completionview', 'completionentries', 'completionusegrade', + 'completionpassgrade', ]; } } diff --git a/mod/glossary/tests/behat/glossary_activity_completion_pass_grade.feature b/mod/glossary/tests/behat/glossary_activity_completion_pass_grade.feature new file mode 100644 index 00000000000..1544057470f --- /dev/null +++ b/mod/glossary/tests/behat/glossary_activity_completion_pass_grade.feature @@ -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" diff --git a/mod/h5pactivity/tests/behat/h5pactivity_completion_pass_grade.feature b/mod/h5pactivity/tests/behat/h5pactivity_completion_pass_grade.feature new file mode 100644 index 00000000000..ac664b555a8 --- /dev/null +++ b/mod/h5pactivity/tests/behat/h5pactivity_completion_pass_grade.feature @@ -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 diff --git a/mod/lesson/classes/completion/custom_completion.php b/mod/lesson/classes/completion/custom_completion.php index c3ce80d264d..ab02a71c96c 100644 --- a/mod/lesson/classes/completion/custom_completion.php +++ b/mod/lesson/classes/completion/custom_completion.php @@ -103,6 +103,7 @@ class custom_completion extends activity_custom_completion { 'completiontimespent', 'completionendreached', 'completionusegrade', + 'completionpassgrade', ]; } } diff --git a/mod/lesson/tests/behat/lesson_completion_pass_grade.feature b/mod/lesson/tests/behat/lesson_completion_pass_grade.feature new file mode 100644 index 00000000000..4b909e09ad1 --- /dev/null +++ b/mod/lesson/tests/behat/lesson_completion_pass_grade.feature @@ -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 diff --git a/mod/lti/tests/behat/lti_completion_pass_grade.feature b/mod/lti/tests/behat/lti_completion_pass_grade.feature new file mode 100644 index 00000000000..e69f74ff996 --- /dev/null +++ b/mod/lti/tests/behat/lti_completion_pass_grade.feature @@ -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 diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php index 0e5e071e19b..db54617df2c 100644 --- a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php +++ b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php @@ -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. diff --git a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php index 6bdb81376ff..31b0fdffb3a 100644 --- a/mod/quiz/backup/moodle2/restore_quiz_stepslib.php +++ b/mod/quiz/backup/moodle2/restore_quiz_stepslib.php @@ -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) { diff --git a/mod/quiz/classes/completion/custom_completion.php b/mod/quiz/classes/completion/custom_completion.php index f8716043fc7..3502a92b378 100644 --- a/mod/quiz/classes/completion/custom_completion.php +++ b/mod/quiz/classes/completion/custom_completion.php @@ -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', ]; } diff --git a/mod/quiz/classes/external.php b/mod/quiz/classes/external.php index afd8194475b..eec1ab81afb 100644 --- a/mod/quiz/classes/external.php +++ b/mod/quiz/classes/external.php @@ -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. diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml index 8d7524e2322..127daed08eb 100644 --- a/mod/quiz/db/install.xml +++ b/mod/quiz/db/install.xml @@ -1,5 +1,5 @@ - @@ -45,7 +45,6 @@ - diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php index a18665aad21..8ae2d577b51 100644 --- a/mod/quiz/db/upgrade.php +++ b/mod/quiz/db/upgrade.php @@ -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; } diff --git a/mod/quiz/deprecatedlib.php b/mod/quiz/deprecatedlib.php index e0afb2e6715..07c39ea8cfc 100644 --- a/mod/quiz/deprecatedlib.php +++ b/mod/quiz/deprecatedlib.php @@ -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; } diff --git a/mod/quiz/lang/en/deprecated.txt b/mod/quiz/lang/en/deprecated.txt index ecadc8221b1..bc4aac1394c 100644 --- a/mod/quiz/lang/en/deprecated.txt +++ b/mod/quiz/lang/en/deprecated.txt @@ -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 diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index e25765faa18..67ed5bdd377 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -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.'; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index c0209589272..9344fae8412 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -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'])); } } diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 9bbc6862d15..e2ce2e526d7 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -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, diff --git a/mod/quiz/mod_form.php b/mod/quiz/mod_form.php index 86bf35bdc80..c4ec0461840 100644 --- a/mod/quiz/mod_form.php +++ b/mod/quiz/mod_form.php @@ -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'), '   ', 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']); } diff --git a/mod/quiz/tests/behat/completion_condition_attempts_used.feature b/mod/quiz/tests/behat/completion_condition_attempts_used.feature index dea3fc19586..63e3b611b27 100644 --- a/mod/quiz/tests/behat/completion_condition_attempts_used.feature +++ b/mod/quiz/tests/behat/completion_condition_attempts_used.feature @@ -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" diff --git a/mod/quiz/tests/behat/completion_condition_passing_grade.feature b/mod/quiz/tests/behat/completion_condition_passing_grade.feature index 4c435129c4f..1ee28b89535 100644 --- a/mod/quiz/tests/behat/completion_condition_passing_grade.feature +++ b/mod/quiz/tests/behat/completion_condition_passing_grade.feature @@ -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" diff --git a/mod/quiz/tests/behat/quiz_activity_completion.feature b/mod/quiz/tests/behat/quiz_activity_completion.feature index 2f6c097b826..63dc6ac21f1 100644 --- a/mod/quiz/tests/behat/quiz_activity_completion.feature +++ b/mod/quiz/tests/behat/quiz_activity_completion.feature @@ -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" diff --git a/mod/quiz/tests/custom_completion_test.php b/mod/quiz/tests/custom_completion_test.php index d47ebc332b2..7407e492677 100644 --- a/mod/quiz/tests/custom_completion_test.php +++ b/mod/quiz/tests/custom_completion_test.php @@ -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( diff --git a/mod/quiz/tests/external_test.php b/mod/quiz/tests/external_test.php index bd4da674d09..70af89ca6f9 100644 --- a/mod/quiz/tests/external_test.php +++ b/mod/quiz/tests/external_test.php @@ -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 = []; diff --git a/mod/quiz/tests/lib_test.php b/mod/quiz/tests/lib_test.php index c751dffc044..07a349e9fb6 100644 --- a/mod/quiz/tests/lib_test.php +++ b/mod/quiz/tests/lib_test.php @@ -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; diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt index 2a2a047a815..e15a91141df 100644 --- a/mod/quiz/upgrade.txt +++ b/mod/quiz/upgrade.txt @@ -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 === diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 218c8dc6621..2ccb6c90770 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021052502; +$plugin->version = 2021052503; $plugin->requires = 2021052500; $plugin->component = 'mod_quiz'; diff --git a/mod/scorm/classes/completion/custom_completion.php b/mod/scorm/classes/completion/custom_completion.php index 9a551915a1a..c5b11058531 100644 --- a/mod/scorm/classes/completion/custom_completion.php +++ b/mod/scorm/classes/completion/custom_completion.php @@ -210,6 +210,7 @@ class custom_completion extends activity_custom_completion { 'completionstatusallscos', 'completionstatusrequired', 'completionusegrade', + 'completionpassgrade', 'completionscorerequired', ]; } diff --git a/mod/scorm/mod_form.php b/mod/scorm/mod_form.php index 18829612aeb..ad6892a8422 100644 --- a/mod/scorm/mod_form.php +++ b/mod/scorm/mod_form.php @@ -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(); } diff --git a/mod/upgrade.txt b/mod/upgrade.txt index 998bce0963d..e702f37948f 100644 --- a/mod/upgrade.txt +++ b/mod/upgrade.txt @@ -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. diff --git a/mod/workshop/mod_form.php b/mod/workshop/mod_form.php index db9a52eaaf6..2860ecf1002 100644 --- a/mod/workshop/mod_form.php +++ b/mod/workshop/mod_form.php @@ -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'); diff --git a/mod/workshop/tests/behat/workshop_completion_pass_grade.feature b/mod/workshop/tests/behat/workshop_completion_pass_grade.feature new file mode 100644 index 00000000000..39e3d493431 --- /dev/null +++ b/mod/workshop/tests/behat/workshop_completion_pass_grade.feature @@ -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 diff --git a/version.php b/version.php index 8315141dcf0..79617dfcc15 100644 --- a/version.php +++ b/version.php @@ -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