diff --git a/blocks/completionstatus/details.php b/blocks/completionstatus/details.php index ec48b4b15ea..b493e1dd862 100644 --- a/blocks/completionstatus/details.php +++ b/blocks/completionstatus/details.php @@ -110,7 +110,36 @@ $params = array( ); $ccompletion = new completion_completion($params); -if ($coursecomplete) { +// Save row data. +$rows = array(); + +// Flag to set if current completion data is inconsistent with what is stored in the database. +$pendingupdate = false; + +// Load criteria to display. +$completions = $info->get_completions($user->id); + +// Loop through course criteria. +foreach ($completions as $completion) { + $criteria = $completion->get_criteria(); + + if (!$pendingupdate && $criteria->is_pending($completion)) { + $pendingupdate = true; + } + + $row = array(); + $row['type'] = $criteria->criteriatype; + $row['title'] = $criteria->get_title(); + $row['status'] = $completion->get_status(); + $row['complete'] = $completion->is_complete(); + $row['timecompleted'] = $completion->timecompleted; + $row['details'] = $criteria->get_details($completion); + $rows[] = $row; +} + +if ($pendingupdate) { + echo html_writer::tag('i', get_string('pending', 'completion')); +} else if ($coursecomplete) { echo get_string('complete'); } else if (!$criteriacomplete && !$ccompletion->timestarted) { echo html_writer::tag('i', get_string('notyetstarted', 'completion')); @@ -121,9 +150,6 @@ if ($coursecomplete) { echo html_writer::end_tag('td'); echo html_writer::end_tag('tr'); -// Load criteria to display. -$completions = $info->get_completions($user->id); - // Check if this course has any criteria. if (empty($completions)) { echo html_writer::start_tag('tr'); @@ -166,23 +192,6 @@ if (empty($completions)) { echo html_writer::tag('th', get_string('completiondate', 'report_completion'), array('class' => 'c5 header', 'scope' => 'col')); echo html_writer::end_tag('tr'); - // Save row data. - $rows = array(); - - // Loop through course criteria. - foreach ($completions as $completion) { - $criteria = $completion->get_criteria(); - - $row = array(); - $row['type'] = $criteria->criteriatype; - $row['title'] = $criteria->get_title(); - $row['status'] = $completion->get_status(); - $row['complete'] = $completion->is_complete(); - $row['timecompleted'] = $completion->timecompleted; - $row['details'] = $criteria->get_details($completion); - $rows[] = $row; - } - // Print table. $last_type = ''; $agg_type = false; diff --git a/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature b/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature index a3094956a65..32f1ca06837 100644 --- a/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature +++ b/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature @@ -133,4 +133,4 @@ Feature: Enable Block Completion in a course using activity completion 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" + And I should see "No" in the "Activity completion" "table_row" diff --git a/completion/classes/api.php b/completion/classes/api.php index 4912f514cfe..d4c83a238ce 100644 --- a/completion/classes/api.php +++ b/completion/classes/api.php @@ -142,6 +142,7 @@ class api { INNER JOIN {course} c ON cr.course = c.id INNER JOIN {context} con ON con.instanceid = c.id INNER JOIN {role_assignments} ra ON ra.contextid = con.id + INNER JOIN {course_modules} cm ON cm.id = cr.moduleinstance INNER JOIN {course_modules_completion} mc ON mc.coursemoduleid = cr.moduleinstance AND mc.userid = ra.userid LEFT JOIN {course_completion_crit_compl} cc ON cc.criteriaid = cr.id AND cc.userid = ra.userid WHERE cr.criteriatype = :criteriatype @@ -150,15 +151,17 @@ class api { AND cc.id IS NULL AND ( mc.completionstate = :completionstate - OR mc.completionstate = :completionstatepass - OR mc.completionstate = :completionstatefail + OR (cm.completionpassgrade = 1 AND mc.completionstate = :completionstatepass1) + OR (cm.completionpassgrade = 0 AND (mc.completionstate = :completionstatepass2 + OR mc.completionstate = :completionstatefail)) )"; $params = [ 'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY, 'contextlevel' => CONTEXT_COURSE, 'completionstate' => COMPLETION_COMPLETE, - 'completionstatepass' => COMPLETION_COMPLETE_PASS, + 'completionstatepass1' => COMPLETION_COMPLETE_PASS, + 'completionstatepass2' => COMPLETION_COMPLETE_PASS, 'completionstatefail' => COMPLETION_COMPLETE_FAIL ]; diff --git a/completion/classes/progress.php b/completion/classes/progress.php index 49702f4d4fd..c95352259f5 100644 --- a/completion/classes/progress.php +++ b/completion/classes/progress.php @@ -80,7 +80,11 @@ class progress { $completed = 0; foreach ($modules as $module) { $data = $completion->get_data($module, true, $userid); - $completed += $data->completionstate == COMPLETION_INCOMPLETE ? 0 : 1; + if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) { + $completed += 0; + } else { + $completed += 1; + }; } return ($completed / $count) * 100; diff --git a/completion/tests/api_test.php b/completion/tests/api_test.php index a18f3706c5e..4875a69f367 100644 --- a/completion/tests/api_test.php +++ b/completion/tests/api_test.php @@ -287,4 +287,101 @@ class api_test extends \advanced_testcase { $this->assertEquals($student1->id, reset($actual)->userid); $this->assertEquals($student2->id, end($actual)->userid); } + + /** + * Test for mark_course_completions_activity_criteria() with different completionpassgrade settings. + * @covers ::mark_course_completions_activity_criteria + */ + public function test_mark_course_completions_activity_criteria_completion_states() { + global $DB, $CFG; + require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php'); + $this->resetAfterTest(true); + + $courses[] = $this->getDataGenerator()->create_course(['shortname' => 'completionpassgradenotset', + 'enablecompletion' => 1]); + $courses[] = $this->getDataGenerator()->create_course(['shortname' => 'completionpassgradeset', + 'enablecompletion' => 1]); + + $student1 = $this->getDataGenerator()->create_user(); + $student2 = $this->getDataGenerator()->create_user(); + + $teacher = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + + foreach ($courses as $course) { + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); + $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id); + + $completioncriteria = [ + 'completionusegrade' => 1, + 'gradepass' => 50 + ]; + + if ($course->shortname == 'completionpassgradeset') { + $completioncriteria['completionpassgrade'] = 1; + } + + /** @var \mod_assign_generator $assigngenerator */ + $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); + $assign = $assigngenerator->create_instance([ + 'course' => $course->id, + 'completion' => COMPLETION_ENABLED, + ] + $completioncriteria); + + $cmassing = get_coursemodule_from_id('assign', $assign->cmid); + $cm = get_coursemodule_from_instance('assign', $assign->id); + $c = new \completion_info($course); + + // Add activity completion criteria. + $criteriadata = new \stdClass(); + $criteriadata->id = $course->id; + $criteriadata->criteria_activity = array(); + // Some activities. + $criteriadata->criteria_activity[$cmassing->id] = 1; + $criterion = new \completion_criteria_activity(); + $criterion->update_config($criteriadata); + + $this->setUser($teacher); + + // Mark user completions. + $completion = new \stdClass(); + $completion->coursemoduleid = $cm->id; + $completion->timemodified = time(); + $completion->viewed = COMPLETION_NOT_VIEWED; + $completion->overrideby = null; + + // Student1 achieved passgrade. + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE_PASS; + $completion->userid = $student1->id; + $c->internal_set_data($cm, $completion, true); + + // Student2 has not achieved passgrade. + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE_FAIL; + $completion->userid = $student2->id; + $c->internal_set_data($cm, $completion, true); + + $actual = $DB->get_records('course_completions', ['course' => $course->id]); + $this->assertEmpty($actual); + + // Run course completions cron. + $coursecompletionid = \core_completion\api::mark_course_completions_activity_criteria(); + $this->assertEquals(0, $coursecompletionid); + $actual = $DB->get_records('course_completions', ['course' => $course->id]); + + if ($course->shortname == 'completionpassgradeset') { + // Only student1 has completed a course. + $this->assertEquals(1, count($actual)); + $this->assertEquals($student1->id, reset($actual)->userid); + } else { + // Both students completed a course. + $this->assertEquals(2, count($actual)); + $this->assertEquals($student1->id, reset($actual)->userid); + $this->assertEquals($student2->id, end($actual)->userid); + } + } + } } diff --git a/completion/tests/behat/course_completion_activity_criteria.feature b/completion/tests/behat/course_completion_activity_criteria.feature new file mode 100644 index 00000000000..998fd1656ef --- /dev/null +++ b/completion/tests/behat/course_completion_activity_criteria.feature @@ -0,0 +1,168 @@ +@block @block_completionstatus @javascript +Feature: Course completion state should match completion criteria + In order to understand the configuration or status of an course's completion + As a user + I need to see the appropriate completion information on course and dashboard pages + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | idnumber | + | teacher1 | Teacher | 1 | teacher1@example.com | T1 | + | teacher2 | Teacher | 2 | teacher1@example.com | T2 | + | student1 | Student | 1 | student1@example.com | S1 | + And the following "courses" exist: + | fullname | shortname | category | enablecompletion | showcompletionconditions | + | Course 1 | C1 | 0 | 1 | 1 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | teacher2 | C1 | teacher | + | student1 | C1 | student | + And the following "activity" exists: + | activity | assign | + | course | C1 | + | section | 1 | + | name | Test assignment name | + | intro | Submit your online text | + | completion | 1 | + | assignsubmission_onlinetext_enabled | 1 | + | grade[modgrade_type] | Point | + | grade[modgrade_point] | 100 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I navigate to "Course completion" in current page administration + And I click on "Condition: Activity completion" "link" + And I set the field "Assignment - Test assignment name" to "1" + And I press "Save changes" + And I add the "Course completion status" block + And I am on the "Test assignment name" "assign activity" page + 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 | 70 | + And I press "Save and return to course" + And I log out + + Scenario: Completion status show match completion criteria when passgrage condition is set. + Given I am on the "Course 1" course page logged in as "student1" + And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo" + And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "todo" + And I should see "Status: Not yet started" in the "Course completion status" "block" + And I am on the "Test assignment name" "assign activity" page + And I press "Add submission" + And I set the following fields to these values: + | Online text | I'm the student1 submission | + And I press "Save changes" + And I press "Submit assignment" + And I press "Continue" + And I log out + And I am on the "Test assignment name" "assign activity" page logged in as teacher1 + And I follow "View all submissions" + And I click on "Grade" "link" in the "Student 1" "table_row" + And I set the following fields to these values: + | Grade out of 100 | 50.0 | + And I press "Save changes" + And I am on the "Course 1" course page + And I navigate to "Reports" in current page administration + And I click on "Activity completion" "link" + And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row" + And I navigate to "Reports" in current page administration + And I click on "Course completion" "link" in the "region-main" "region" + And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row" + And "Student 1, Course complete: Not completed" "icon" should exist in the "Student 1" "table_row" + And I log out + When I am on the "Course 1" course page logged in as "student1" + And I should see "Status: Pending" in the "Course completion status" "block" + 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 am on the "My courses" page + And I should not see "100%" in the "Course overview" "block" + And I log out + And I am on the "Test assignment name" "assign activity" page logged in as teacher1 + And I follow "View all submissions" + And I click on "Grade" "link" in the "Student 1" "table_row" + And I set the following fields to these values: + | Grade out of 100 | 75.0 | + And I press "Save changes" + And I am on the "Course 1" course page + And I navigate to "Reports" in current page administration + And I click on "Activity completion" "link" + And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row" + And I navigate to "Reports" in current page administration + And I click on "Course completion" "link" in the "region-main" "region" + And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row" + And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row" + And I log out + And I am on the "Course 1" course page logged in as "student1" + And I should see "Status: Complete" in the "Course completion status" "block" + 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 am on the "My courses" page + Then I should see "100%" in the "Course overview" "block" + + Scenario: Completion status show match completion criteria when passgrage condition is not set. + Given I am on the "Course 1" course page logged in as "teacher1" + And I am on the "Test assignment name" "assign activity" page + 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 | 0 | + | gradepass | 70 | + And I press "Save and return to course" + And I log out + And I am on the "Course 1" course page logged in as "student1" + And the "Receive a grade" completion condition of "Test assignment name" is displayed as "todo" + And I should see "Status: Not yet started" in the "Course completion status" "block" + And I am on the "Test assignment name" "assign activity" page + And I press "Add submission" + And I set the following fields to these values: + | Online text | I'm the student1 submission | + And I press "Save changes" + And I press "Submit assignment" + And I press "Continue" + And I log out + And I am on the "Test assignment name" "assign activity" page logged in as teacher1 + And I follow "View all submissions" + And I click on "Grade" "link" in the "Student 1" "table_row" + And I set the following fields to these values: + | Grade out of 100 | 50.0 | + And I press "Save changes" + And I am on the "Course 1" course page + And I navigate to "Reports" in current page administration + And I click on "Activity completion" "link" + And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row" + And I navigate to "Reports" in current page administration + And I click on "Course completion" "link" in the "region-main" "region" + And "Student 1, Test assignment name: Completed (did not achieve pass grade)" "icon" should exist in the "Student 1" "table_row" + And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row" + And I log out + When I am on the "Course 1" course page logged in as "student1" + And I should see "Status: Complete" in the "Course completion status" "block" + # Once MDL-75582 is fixed "failed" should be changed to "done" + And the "Receive a grade" completion condition of "Test assignment name" is displayed as "failed" + And I am on the "My courses" page + And I should see "100%" in the "Course overview" "block" + And I log out + And I am on the "Test assignment name" "assign activity" page logged in as teacher1 + And I follow "View all submissions" + And I click on "Grade" "link" in the "Student 1" "table_row" + And I set the following fields to these values: + | Grade out of 100 | 75.0 | + And I press "Save changes" + And I am on the "Course 1" course page + And I navigate to "Reports" in current page administration + And I click on "Activity completion" "link" + And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row" + And I navigate to "Reports" in current page administration + And I click on "Course completion" "link" in the "region-main" "region" + And "Student 1, Test assignment name: Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row" + And "Student 1, Course complete: Completed" "icon" should exist in the "Student 1" "table_row" + And I log out + And I am on the "Course 1" course page logged in as "student1" + And I should see "Status: Complete" in the "Course completion status" "block" + And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done" + And I am on the "My courses" page + Then I should see "100%" in the "Course overview" "block" diff --git a/completion/tests/progress_test.php b/completion/tests/progress_test.php index 08785fa3b25..16f77290acd 100644 --- a/completion/tests/progress_test.php +++ b/completion/tests/progress_test.php @@ -120,6 +120,114 @@ class progress_test extends \advanced_testcase { $this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id)); } + /** + * Tests that the course progress percentage is returned correctly for various grade to pass settings + * + * @covers \core_completion\progress::get_course_progress_percentage. + */ + public function test_course_progress_percentage_completion_state() { + global $DB; + + // Add a course that supports completion. + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + + // Enrol a user in the course. + $teacher = $this->getDataGenerator()->create_user(); + $user = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id); + $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); + + // Add three activities that use completion. + /** @var \mod_assign_generator $assigngenerator */ + $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); + $assign['passgragepassed'] = $assigngenerator->create_instance([ + 'course' => $course->id, + 'completion' => COMPLETION_ENABLED, + 'completionusegrade' => 1, + 'gradepass' => 50, + 'completionpassgrade' => 1 + ]); + + $assign['passgragefailed'] = $assigngenerator->create_instance([ + 'course' => $course->id, + 'completion' => COMPLETION_ENABLED, + 'completionusegrade' => 1, + 'gradepass' => 50, + 'completionpassgrade' => 1 + ]); + + $assign['passgragenotused'] = $assigngenerator->create_instance([ + 'course' => $course->id, + 'completion' => COMPLETION_ENABLED, + 'completionusegrade' => 1, + 'gradepass' => 50, + ]); + + $assign['nograde'] = $assigngenerator->create_instance([ + 'course' => $course->id, + 'completion' => COMPLETION_ENABLED, + ]); + + $c = new \completion_info($course); + + foreach ($assign as $item) { + $cmassing = get_coursemodule_from_id('assign', $item->cmid); + + // Add activity completion criteria. + $criteriadata = new \stdClass(); + $criteriadata->id = $course->id; + $criteriadata->criteria_activity = []; + // Some activities. + $criteriadata->criteria_activity[$cmassing->id] = 1; + $criterion = new \completion_criteria_activity(); + $criterion->update_config($criteriadata); + } + + $this->setUser($teacher); + + foreach ($assign as $key => $item) { + $cm = get_coursemodule_from_instance('assign', $item->id); + + // Mark user completions. + $completion = new \stdClass(); + $completion->coursemoduleid = $cm->id; + $completion->timemodified = time(); + $completion->viewed = COMPLETION_NOT_VIEWED; + $completion->overrideby = null; + + if ($key == 'passgragepassed') { + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE_PASS; + $completion->userid = $user->id; + $c->internal_set_data($cm, $completion, true); + } else if ($key == 'passgragefailed') { + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE_FAIL; + $completion->userid = $user->id; + $c->internal_set_data($cm, $completion, true); + } else if ($key == 'passgragenotused') { + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE; + $completion->userid = $user->id; + $c->internal_set_data($cm, $completion, true); + } else if ($key == 'nograde') { + $completion->id = 0; + $completion->completionstate = COMPLETION_COMPLETE; + $completion->userid = $user->id; + $c->internal_set_data($cm, $completion, true); + } + } + + // Run course completions cron. + \core_completion\api::mark_course_completions_activity_criteria(); + + // Check we have received valid data. + // Only assign2 is not completed. + $this->assertEquals('75', \core_completion\progress::get_course_progress_percentage($course, $user->id)); + } + /** * Tests that the course progress returns null when the course does not support it. */