Skip to content

Commit

Permalink
MDL-37993 Quiz: completion upon all passing grade or attempts exhausted
Browse files Browse the repository at this point in the history
This patch adds completion options to Quiz similar to what is available in scorm.
One can have the quiz marked complete when either a passing grade is achieved or
all attempts are used up. This will allow a quiz to complete when the user "passes
or fails".  (Where "fail" means "using up all attempts without passing".)
  • Loading branch information
sensei-hacker committed Jul 14, 2014
1 parent 7784c3a commit db3686d
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 2 deletions.
2 changes: 1 addition & 1 deletion mod/quiz/backup/moodle2/backup_quiz_stepslib.php
Expand Up @@ -49,7 +49,7 @@ protected function define_structure() {
'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);
Expand Down
2 changes: 2 additions & 0 deletions mod/quiz/db/install.xml
Expand Up @@ -44,6 +44,8 @@
<FIELD NAME="delay2" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Delay that must be left between the second and subsequent attempt, in seconds."/>
<FIELD NAME="showuserpicture" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Option to show the user's picture during the attempt and on the review page."/>
<FIELD NAME="showblocks" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether blocks should be shown on the attempt.php and review.php pages."/>
<FIELD NAME="completionattemptsexhausted" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionpass" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
27 changes: 27 additions & 0 deletions mod/quiz/db/upgrade.php
Expand Up @@ -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;
}

4 changes: 4 additions & 0 deletions mod/quiz/lang/en/quiz.php
Expand Up @@ -152,6 +152,10 @@
$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.';
Expand Down
50 changes: 50 additions & 0 deletions mod/quiz/lib.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions mod/quiz/locallib.php
Expand Up @@ -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);
}
Expand Down
33 changes: 33 additions & 0 deletions mod/quiz/mod_form.php
Expand Up @@ -585,4 +585,37 @@ public function validation($data, $files) {

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']);
}
}
88 changes: 88 additions & 0 deletions 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"

87 changes: 87 additions & 0 deletions 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"
2 changes: 1 addition & 1 deletion mod/quiz/version.php
Expand Up @@ -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;

0 comments on commit db3686d

Please sign in to comment.