diff --git a/mod/quiz/backup/moodle2/backup_quiz_stepslib.php b/mod/quiz/backup/moodle2/backup_quiz_stepslib.php
index 6cd03e6daf7..aaa9e01b95d 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', 'shufflequestions', 'shuffleanswers',
'sumgrades', 'grade', 'timecreated',
'timemodified', 'password', 'subnet', 'browsersecurity',
- 'delay1', 'delay2', 'showuserpicture', 'showblocks'));
+ 'delay1', 'delay2', 'showuserpicture', 'showblocks', 'completionattemptsexhausted', 'completionpass'));
// Define elements for access rule subplugin settings.
$this->add_subplugin_structure('quizaccess', $quiz, true);
diff --git a/mod/quiz/db/install.xml b/mod/quiz/db/install.xml
index 14f2fde8dc2..85fe13d328d 100644
--- a/mod/quiz/db/install.xml
+++ b/mod/quiz/db/install.xml
@@ -44,6 +44,8 @@
+
+
diff --git a/mod/quiz/db/upgrade.php b/mod/quiz/db/upgrade.php
index 0a8ba8d45ff..2110673a26d 100644
--- a/mod/quiz/db/upgrade.php
+++ b/mod/quiz/db/upgrade.php
@@ -776,6 +776,33 @@ function xmldb_quiz_upgrade($oldversion) {
// Moodle v2.7.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2014052800) {
+
+ // Define field completionattemptsexhausted to be added to quiz.
+ $table = new xmldb_table('quiz');
+ $field = new xmldb_field('completionattemptsexhausted', XMLDB_TYPE_INTEGER, '1', null, null, null, '0', 'showblocks');
+
+ // Conditionally launch add field completionattemptsexhausted.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ // Quiz savepoint reached.
+ upgrade_mod_savepoint(true, 2014052800, 'quiz');
+ }
+
+ if ($oldversion < 2014052801) {
+ // Define field completionpass to be added to quiz.
+ $table = new xmldb_table('quiz');
+ $field = new xmldb_field('completionpass', XMLDB_TYPE_INTEGER, '1', null, null, null, 0, 'completionattemptsexhausted');
+
+ // Conditionally launch add field completionpass.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Quiz savepoint reached.
+ upgrade_mod_savepoint(true, 2014052801, 'quiz');
+ }
return true;
}
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index 2ebae5fd6ae..22d47845504 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -152,6 +152,10 @@ $string['comment'] = 'Comment';
$string['commentorgrade'] = 'Make comment or override grade';
$string['comments'] = 'Comments';
$string['completedon'] = 'Completed on';
+$string['completionpass'] = 'Require passing grade';
+$string['completionpass_help'] = 'If enabled, this activity is considered complete when the student receives a passing grade, with the pass grade set in the gradebook.';
+$string['completionattemptsexhausted'] = 'Or all available attempts completed';
+$string['completionattemptsexhausted_help'] = 'Mark quiz complete when the student has exhausted the maximum number of attempts.';
$string['configadaptive'] = 'If you choose Yes for this option then the student will be allowed multiple responses to a question even within the same attempt at the quiz.';
$string['configattemptsallowed'] = 'Restriction on the number of attempts students are allowed at the quiz.';
$string['configdecimaldigits'] = 'Number of digits that should be shown after the decimal point when displaying grades.';
diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php
index fdfb41835bd..c5b9a7b49d8 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -1560,6 +1560,7 @@ function quiz_supports($feature) {
case FEATURE_GROUPMEMBERSONLY: return true;
case FEATURE_MOD_INTRO: return true;
case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
+ case FEATURE_COMPLETION_HAS_RULES: return true;
case FEATURE_GRADE_HAS_GRADE: return true;
case FEATURE_GRADE_OUTCOMES: return true;
case FEATURE_BACKUP_MOODLE2: return true;
@@ -1790,3 +1791,52 @@ function quiz_get_navigation_options() {
QUIZ_NAVMETHOD_SEQ => get_string('navmethod_seq', 'quiz')
);
}
+
+
+/**
+ * Obtains the automatic completion state for this quiz on any conditions
+ * in quiz settings, such as if all attempts are used or a certain grade is achieved.
+ *
+ * @param object $course Course
+ * @param object $cm Course-module
+ * @param int $userid User ID
+ * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
+ * @return bool True if completed, false if not. (If no conditions, then return
+ * value depends on comparison type)
+ */
+function quiz_get_completion_state($course, $cm, $userid, $type) {
+ global $DB;
+ global $CFG;
+
+ $quiz = $DB->get_record('quiz', array('id' => $cm->instance), '*', MUST_EXIST);
+ if (!$quiz->completionattemptsexhausted && !$quiz->completionpass) {
+ return $type;
+ }
+
+ // Check if the user has used up all attempts.
+ if ($quiz->completionattemptsexhausted) {
+ $attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
+ if ($attempts) {
+ $lastfinishedattempt = end($attempts);
+ $context = context_module::instance($cm->id);
+ $quizobj = quiz::create($quiz->id, $userid);
+ $accessmanager = new quiz_access_manager($quizobj, time(),
+ has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
+ if ($accessmanager->is_finished(count($attempts), $lastfinishedattempt)) {
+ return true;
+ }
+ }
+ }
+
+ // Check for passing grade.
+ if ($quiz->completionpass) {
+ require_once($CFG->libdir . '/gradelib.php');
+ $item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
+ 'itemmodule' => 'quiz', 'iteminstance' => $cm->instance));
+ if ($item) {
+ $grades = grade_grade::fetch_users_grades($item, array($userid), false);
+ return $grades[$userid]->is_passed($item);
+ }
+ }
+ return false;
+}
diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php
index ffa7f86cfe5..ce078fa9abc 100644
--- a/mod/quiz/locallib.php
+++ b/mod/quiz/locallib.php
@@ -1641,6 +1641,11 @@ function quiz_attempt_submitted_handler($event) {
return true;
}
+ // Update completion state.
+ $completion = new completion_info($course);
+ if ($completion->is_enabled($cm) && ($quiz->completionattemptsexhausted || $quiz->completionpass)) {
+ $completion->update_state($cm, COMPLETION_COMPLETE, $event->userid);
+ }
return quiz_send_notification_messages($course, $quiz, $attempt,
context_module::instance($cm->id), $cm);
}
diff --git a/mod/quiz/mod_form.php b/mod/quiz/mod_form.php
index efd0e8ac813..8475c323077 100644
--- a/mod/quiz/mod_form.php
+++ b/mod/quiz/mod_form.php
@@ -585,4 +585,37 @@ class mod_quiz_mod_form extends moodleform_mod {
return $errors;
}
+
+ /**
+ * Display module-specific activity completion rules.
+ * Part of the API defined by moodleform_mod
+ * @return array Array of string IDs of added items, empty array if none
+ */
+ public function add_completion_rules() {
+ $mform = $this->_form;
+ $items = array();
+
+ $group = array();
+ $group[] = $mform->createElement('advcheckbox', 'completionpass', null, get_string('completionpass', 'quiz'),
+ array('group' => 'cpass'));
+
+ $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';
+ return $items;
+ }
+
+ /**
+ * Called during validation. Indicates whether a module-specific completion rule is selected.
+ *
+ * @param array $data Input data (not yet validated)
+ * @return bool True if one or more rules is enabled, false if none are.
+ */
+ public function completion_rule_enabled($data) {
+ return !empty($data['completionattemptsexhausted']) || !empty($data['completionpass']);
+ }
}
diff --git a/mod/quiz/tests/behat/completion_condition_attempts_used.feature b/mod/quiz/tests/behat/completion_condition_attempts_used.feature
new file mode 100644
index 00000000000..d1684dd6404
--- /dev/null
+++ b/mod/quiz/tests/behat/completion_condition_attempts_used.feature
@@ -0,0 +1,88 @@
+@mod @mod_quiz
+Feature: Set a quiz to be marked complete when the student uses all attempts allowed
+ In order to ensure a student has learned the material before being marked complete
+ As a teacher
+ I need to set a quiz to complete when the student receives a passing grade, or completed_fail if they use all attempts without passing
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@asd.com |
+ | teacher1 | Teacher | 1 | teacher1@asd.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | Enable completion tracking | 1 |
+ And I expand "Grades" node
+ And I follow "Grade item settings"
+ And I set the field "Advanced grade item options" to "hiddenuntil"
+ And I press "Save changes"
+ And I log out
+
+ Scenario: student1 uses up both attempts without passing
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I click on "Edit settings" "link" in the "Administration" "block"
+ And I set the following fields to these values:
+ | Enable completion tracking | Yes |
+ And I press "Save changes"
+ And I add a "Quiz" to section "1" and I fill the form with:
+ | Name | Test quiz name |
+ | Description | Test quiz description |
+ | Completion tracking | Show activity as complete when conditions are met |
+ | Attempts allowed | 2 |
+ | Require passing grade | 1 |
+ | Or all available attempts completed | 1 |
+ And I add a "True/False" question to the "Test quiz name" quiz with:
+ | Question name | First question |
+ | Question text | Answer the first question |
+ | General feedback | Thank you, this is the general feedback |
+ | Correct answer | True |
+ | Feedback for the response 'True'. | So you think it is true |
+ | Feedback for the response 'False'. | So you think it is false |
+ And I follow "Course 1"
+ And I follow "Grades"
+ And I follow "Simple view"
+ And I follow "Edit quiz Test quiz name"
+ Then I should see "Edit grade item"
+ And I set the field "gradepass" to "5"
+ And I press "Save changes"
+ And I should see "Simple view"
+ Then I log out
+
+ And I log in as "student1"
+ And I follow "Course 1"
+ And "//img[contains(@alt, 'Not completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I follow "Test quiz name"
+ And I press "Attempt quiz now"
+ And I should see "Question 1"
+ And I should see "Answer the first question"
+ And I set the field "False" to "1"
+ And I press "Next"
+ And I should see "Answer saved"
+ And I press "Submit all and finish"
+ And I follow "C1"
+ And "//img[contains(@alt, 'Not completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I follow "Test quiz name"
+ And I press "Re-attempt quiz"
+ Then I should see "Question 1"
+ And I should see "Answer the first question"
+ And I set the field "False" to "1"
+ And I press "Next"
+ And I should see "Answer saved"
+ And I press "Submit all and finish"
+ And I follow "C1"
+ And "//img[contains(@alt, 'Completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Activity completion"
+ Then "//img[contains(@title,'Test quiz name') and @alt='Completed']" "xpath_element" should exist in the "Student 1" "table_row"
+
diff --git a/mod/quiz/tests/behat/completion_condition_passing_grade.feature b/mod/quiz/tests/behat/completion_condition_passing_grade.feature
new file mode 100644
index 00000000000..38fa5d3dbcd
--- /dev/null
+++ b/mod/quiz/tests/behat/completion_condition_passing_grade.feature
@@ -0,0 +1,87 @@
+@mod @mod_quiz
+Feature: Set a quiz to be marked complete when the student passes
+ In order to ensure a student has learned the material before being marked complete
+ As a teacher
+ I need to set a quiz to complete when the student recieves a passing grade
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@asd.com |
+ | teacher1 | Teacher | 1 | teacher1@asd.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | Enable completion tracking | 1 |
+ And I expand "Grades" node
+ And I follow "Grade item settings"
+ And I set the field "Advanced grade item options" to "hiddenuntil"
+ And I press "Save changes"
+ And I log out
+
+ Scenario: student1 passes on the first try
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I click on "Edit settings" "link" in the "Administration" "block"
+ And I set the following fields to these values:
+ | Enable completion tracking | Yes |
+ And I press "Save changes"
+ And I add a "Quiz" to section "1" and I fill the form with:
+ | Name | Test quiz name |
+ | Description | Test quiz description |
+ | Completion tracking | Show activity as complete when conditions are met |
+ | Attempts allowed | 4 |
+ | Require passing grade | 1 |
+ And I add a "True/False" question to the "Test quiz name" quiz with:
+ | Question name | First question |
+ | Question text | Answer the first question |
+ | General feedback | Thank you, this is the general feedback |
+ | Correct answer | True |
+ | Feedback for the response 'True'. | So you think it is true |
+ | Feedback for the response 'False'. | So you think it is false |
+ And I follow "Course 1"
+ And I follow "Grades"
+ And I select "Simple view" from "jump"
+ And I press "Go"
+ And I follow "Edit quiz Test quiz name"
+ Then I should see "Edit grade item"
+ And I set the field "gradepass" to "5"
+ And I press "Save changes"
+ Then I should see "Simple view"
+ And I log out
+
+ And I log in as "student1"
+ And I follow "Course 1"
+ And "//img[contains(@alt, 'Not completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I follow "Test quiz name"
+ And I press "Attempt quiz now"
+ Then I should see "Question 1"
+ And I should see "Answer the first question"
+ And I set the field "False" to "1"
+ And I press "Next"
+ And I should see "Answer saved"
+ And I press "Submit all and finish"
+ And I follow "C1"
+ And "//img[contains(@alt, 'Not completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I follow "Test quiz name"
+ And I press "Re-attempt quiz"
+ Then I should see "Question 1"
+ And I should see "Answer the first question"
+ And I set the field "True" to "1"
+ And I press "Next"
+ And I should see "Answer saved"
+ And I press "Submit all and finish"
+ And I follow "C1"
+ And "//img[contains(@alt, 'Completed: Test quiz name')]" "xpath_element" should exist in the "li.modtype_quiz" "css_element"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Activity completion"
+ Then "//img[contains(@title,'Test quiz name') and @alt='Completed']" "xpath_element" should exist in the "Student 1" "table_row"
diff --git a/mod/quiz/version.php b/mod/quiz/version.php
index 06828f87529..0d368c88427 100644
--- a/mod/quiz/version.php
+++ b/mod/quiz/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2014051200; // The current module version (Date: YYYYMMDDXX).
+$plugin->version = 2014052801; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2014050800; // Requires this Moodle version.
$plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
$plugin->cron = 60;