mirror of
https://github.com/moodle/moodle.git
synced 2025-06-01 13:46:35 +02:00
MDL-45002 mod_quiz: completion option, at least N attempts completed
This commit is contained in:
parent
ee23a8cf25
commit
760ed019ee
@ -50,7 +50,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
|
||||
'sumgrades', 'grade', 'timecreated',
|
||||
'timemodified', 'password', 'subnet', 'browsersecurity',
|
||||
'delay1', 'delay2', 'showuserpicture', 'showblocks', 'completionattemptsexhausted', 'completionpass',
|
||||
'allowofflineattempts'));
|
||||
'completionminattempts', 'allowofflineattempts'));
|
||||
|
||||
// Define elements for access rule subplugin settings.
|
||||
$this->add_subplugin_structure('quizaccess', $quiz, true);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20180719" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20200615" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -46,6 +46,7 @@
|
||||
<FIELD NAME="showblocks" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether blocks should be shown on the attempt.php and review.php pages."/>
|
||||
<FIELD NAME="completionattemptsexhausted" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="completionpass" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="completionminattempts" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="allowofflineattempts" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Whether to allow the quiz to be attempted offline in the mobile app"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
|
@ -29,7 +29,8 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @param string $oldversion the version we are upgrading from.
|
||||
*/
|
||||
function xmldb_quiz_upgrade($oldversion) {
|
||||
global $CFG;
|
||||
global $CFG, $DB;
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
// Automatically generated Moodle v3.5.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
@ -46,5 +47,21 @@ function xmldb_quiz_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.9.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2020061501) {
|
||||
|
||||
// Define field completionminattempts to be added to quiz.
|
||||
$table = new xmldb_table('quiz');
|
||||
$field = new xmldb_field('completionminattempts', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0',
|
||||
'completionpass');
|
||||
|
||||
// Conditionally launch add field completionminattempts.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2020061501, 'quiz');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -179,6 +179,9 @@ $string['comment'] = 'Comment';
|
||||
$string['commentorgrade'] = 'Make comment or override grade';
|
||||
$string['comments'] = 'Comments';
|
||||
$string['completedon'] = 'Completed on';
|
||||
$string['completionminattempts'] = 'Students must send attempts:';
|
||||
$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.';
|
||||
|
105
mod/quiz/lib.php
105
mod/quiz/lib.php
@ -1128,6 +1128,9 @@ function quiz_process_options($quiz) {
|
||||
if (empty($quiz->completionpass)) {
|
||||
$quiz->completionattemptsexhausted = 0;
|
||||
}
|
||||
if (empty($quiz->completionminattemptsenabled)) {
|
||||
$quiz->completionminattempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1894,6 +1897,74 @@ function quiz_get_navigation_options() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used in quiz_get_completion_state. Check passing grade (or no attempts left) requirement for completion.
|
||||
*
|
||||
* @param object $course
|
||||
* @param object $cm
|
||||
* @param int $userid
|
||||
* @param object $quiz
|
||||
* @return bool True if the passing grade (or no attempts left) requirement is disabled or met.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz) {
|
||||
global $CFG;
|
||||
|
||||
if (!$quiz->completionpass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for passing grade.
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
$item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
|
||||
'itemmodule' => 'quiz', 'iteminstance' => $cm->instance, 'outcomeid' => null));
|
||||
if ($item) {
|
||||
$grades = grade_grade::fetch_users_grades($item, array($userid), false);
|
||||
if (!empty($grades[$userid]) && $grades[$userid]->is_passed($item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If a passing grade is required and exhausting all available attempts is not accepted for completion,
|
||||
// then this quiz is not complete.
|
||||
if (!$quiz->completionattemptsexhausted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if all attempts are used up.
|
||||
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
|
||||
if (!$attempts) {
|
||||
return false;
|
||||
}
|
||||
$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));
|
||||
|
||||
return $accessmanager->is_finished(count($attempts), $lastfinishedattempt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used in quiz_get_completion_state. Check minimum attempts requirement for completion.
|
||||
*
|
||||
* @param int $userid
|
||||
* @param object $quiz
|
||||
* @return bool True if minimum attempts requirement is disabled or met.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
function quiz_completion_check_min_attempts($userid, $quiz) {
|
||||
global $DB;
|
||||
|
||||
if (empty($quiz->completionminattempts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the user has done enough attempts.
|
||||
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
|
||||
return $quiz->completionminattempts <= count($attempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -1907,41 +1978,21 @@ function quiz_get_navigation_options() {
|
||||
*/
|
||||
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) {
|
||||
if (!$quiz->completionattemptsexhausted && !$quiz->completionpass && !$quiz->completionminattempts) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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, 'outcomeid' => null));
|
||||
if ($item) {
|
||||
$grades = grade_grade::fetch_users_grades($item, array($userid), false);
|
||||
if (!empty($grades[$userid])) {
|
||||
return $grades[$userid]->is_passed($item);
|
||||
}
|
||||
}
|
||||
if (!quiz_completion_check_min_attempts($userid, $quiz)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1799,7 +1799,8 @@ function quiz_attempt_submitted_handler($event) {
|
||||
|
||||
// Update completion state.
|
||||
$completion = new completion_info($course);
|
||||
if ($completion->is_enabled($cm) && ($quiz->completionattemptsexhausted || $quiz->completionpass)) {
|
||||
if ($completion->is_enabled($cm) &&
|
||||
($quiz->completionattemptsexhausted || $quiz->completionpass || $quiz->completionminattempts)) {
|
||||
$completion->update_state($cm, COMPLETION_COMPLETE, $event->userid);
|
||||
}
|
||||
return quiz_send_notification_messages($course, $quiz, $attempt,
|
||||
|
@ -481,6 +481,31 @@ class mod_quiz_mod_form extends moodleform_mod {
|
||||
$toform[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($toform['completionminattempts'])) {
|
||||
$toform['completionminattempts'] = 1;
|
||||
} else {
|
||||
$toform['completionminattemptsenabled'] = $toform['completionminattempts'] > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows module to modify the data returned by form get_data().
|
||||
* This method is also called in the bulk activity completion form.
|
||||
*
|
||||
* Only available on moodleform_mod.
|
||||
*
|
||||
* @param stdClass $data the form data to be modified.
|
||||
*/
|
||||
public function data_postprocessing($data) {
|
||||
parent::data_postprocessing($data);
|
||||
if (!empty($data->completionunlocked)) {
|
||||
// Turn off completion settings if the checkboxes aren't ticked.
|
||||
$autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC;
|
||||
if (empty($data->completionminattemptsenabled) || !$autocompletion) {
|
||||
$data->completionminattempts = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validation($data, $files) {
|
||||
@ -513,6 +538,12 @@ class mod_quiz_mod_form extends moodleform_mod {
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['completionminattempts'])) {
|
||||
if ($data['attempts'] > 0 && $data['completionminattempts'] > $data['attempts']) {
|
||||
$errors['completionminattemptsgroup'] = get_string('completionminattemptserror', 'quiz');
|
||||
}
|
||||
}
|
||||
|
||||
// Check the boundary value is a number or a percentage, and in range.
|
||||
$i = 0;
|
||||
while (!empty($data['feedbackboundaries'][$i] )) {
|
||||
@ -593,6 +624,17 @@ class mod_quiz_mod_form extends moodleform_mod {
|
||||
$mform->addGroup($group, 'completionpassgroup', get_string('completionpass', 'quiz'), ' ', false);
|
||||
$mform->addHelpButton('completionpassgroup', 'completionpass', 'quiz');
|
||||
$items[] = 'completionpassgroup';
|
||||
|
||||
$group = array();
|
||||
$group[] = $mform->createElement('checkbox', 'completionminattemptsenabled', '',
|
||||
get_string('completionminattempts', 'quiz'));
|
||||
$group[] = $mform->createElement('text', 'completionminattempts', '', array('size' => 3));
|
||||
$mform->setType('completionminattempts', PARAM_INT);
|
||||
$mform->addGroup($group, 'completionminattemptsgroup', get_string('completionminattemptsgroup', 'quiz'), array(' '), false);
|
||||
$mform->disabledIf('completionminattempts', 'completionminattemptsenabled', 'notchecked');
|
||||
|
||||
$items[] = 'completionminattemptsgroup';
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
@ -603,7 +645,9 @@ class mod_quiz_mod_form extends moodleform_mod {
|
||||
* @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']);
|
||||
return !empty($data['completionattemptsexhausted']) ||
|
||||
!empty($data['completionpass']) ||
|
||||
!empty($data['completionminattemptsenabled']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,56 @@
|
||||
@mod @mod_quiz
|
||||
Feature: Set a quiz to be marked complete when the student completes a minimum amount of attempts
|
||||
In order to ensure a student has completed the quiz before being marked complete
|
||||
As a teacher
|
||||
I need to set a quiz to complete when the student completes a certain amount of attempts
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following config values are set as admin:
|
||||
| grade_item_advanced | hiddenuntil |
|
||||
And the following "question categories" exist:
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | questiontext |
|
||||
| Test questions | truefalse | First question | Answer the first question |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber | completion | completionminattemptsenabled | completionminattempts |
|
||||
| quiz | Test quiz name | C1 | quiz1 | 2 | 1 | 2 |
|
||||
And quiz "Test quiz name" contains the following questions:
|
||||
| question | page |
|
||||
| First question | 1 |
|
||||
And user "student1" has attempted "Test quiz name" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
|
||||
Scenario: student1 uses up both attempts without passing
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And "Completed: Test quiz name" "icon" should not exist in the "Test quiz name" "list_item"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And the "Test quiz name" "quiz" activity with "auto" completion should be marked as not complete
|
||||
And I follow "Test quiz name"
|
||||
And I press "Re-attempt quiz"
|
||||
And I set the field "False" to "1"
|
||||
And I press "Finish attempt ..."
|
||||
And I press "Submit all and finish"
|
||||
And I am on "Course 1" course homepage
|
||||
Then "Completed: Test quiz name" "icon" should exist in the "Test quiz name" "list_item"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Reports > Activity completion" in current page administration
|
||||
And "Completed" "icon" should exist in the "Student 1" "table_row"
|
@ -130,105 +130,270 @@ class mod_quiz_lib_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test checking the completion state of a quiz.
|
||||
* Setup function for all test_quiz_get_completion_state_* tests.
|
||||
*
|
||||
* @param array $completionoptions ['nbstudents'] => int, ['qtype'] => string, ['quizoptions'] => array
|
||||
* @throws dml_exception
|
||||
* @return array [$course, $students, $quiz, $cm]
|
||||
*/
|
||||
public function test_quiz_get_completion_state() {
|
||||
private function setup_quiz_for_testing_completion(array $completionoptions) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Enable completion before creating modules, otherwise the completion data is not written in DB.
|
||||
$CFG->enablecompletion = true;
|
||||
|
||||
// Create a course and student.
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
|
||||
$passstudent = $this->getDataGenerator()->create_user();
|
||||
$failstudent = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->assertNotEmpty($studentrole);
|
||||
// Create a course and students.
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$course = $this->getDataGenerator()->create_course(['enablecompletion' => true]);
|
||||
$students = [];
|
||||
for ($i = 0; $i < $completionoptions['nbstudents']; $i++) {
|
||||
$students[$i] = $this->getDataGenerator()->create_user();
|
||||
$this->assertTrue($this->getDataGenerator()->enrol_user($students[$i]->id, $course->id, $studentrole->id));
|
||||
}
|
||||
|
||||
// Enrol students.
|
||||
$this->assertTrue($this->getDataGenerator()->enrol_user($passstudent->id, $course->id, $studentrole->id));
|
||||
$this->assertTrue($this->getDataGenerator()->enrol_user($failstudent->id, $course->id, $studentrole->id));
|
||||
|
||||
// Make a scale and an outcome.
|
||||
$scale = $this->getDataGenerator()->create_scale();
|
||||
$data = array('courseid' => $course->id,
|
||||
'fullname' => 'Team work',
|
||||
'shortname' => 'Team work',
|
||||
'scaleid' => $scale->id);
|
||||
$outcome = $this->getDataGenerator()->create_grade_outcome($data);
|
||||
|
||||
// Make a quiz with the outcome on.
|
||||
// Make a quiz.
|
||||
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$data = array('course' => $course->id,
|
||||
'outcome_'.$outcome->id => 1,
|
||||
'grade' => 100.0,
|
||||
'questionsperpage' => 0,
|
||||
'sumgrades' => 1,
|
||||
'completion' => COMPLETION_TRACKING_AUTOMATIC,
|
||||
'completionusegrade' => 1,
|
||||
'completionpass' => 1);
|
||||
$data = array_merge([
|
||||
'course' => $course->id,
|
||||
'grade' => 100.0,
|
||||
'questionsperpage' => 0,
|
||||
'sumgrades' => 1,
|
||||
'completion' => COMPLETION_TRACKING_AUTOMATIC
|
||||
], $completionoptions['quizoptions']);
|
||||
$quiz = $quizgenerator->create_instance($data);
|
||||
$cm = get_coursemodule_from_id('quiz', $quiz->cmid);
|
||||
|
||||
// Create a couple of questions.
|
||||
// Create a question.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$question = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
|
||||
$question = $questiongenerator->create_question($completionoptions['qtype'], null, ['category' => $cat->id]);
|
||||
quiz_add_quiz_question($question->id, $quiz);
|
||||
|
||||
$quizobj = quiz::create($quiz->id, $passstudent->id);
|
||||
|
||||
// Set grade to pass.
|
||||
$item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
|
||||
'itemmodule' => 'quiz', 'iteminstance' => $quiz->id, 'outcomeid' => null));
|
||||
$item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'quiz',
|
||||
'iteminstance' => $quiz->id, 'outcomeid' => null]);
|
||||
$item->gradepass = 80;
|
||||
$item->update();
|
||||
|
||||
return [
|
||||
$course,
|
||||
$students,
|
||||
$quiz,
|
||||
$cm
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for all test_quiz_get_completion_state_* tests.
|
||||
* Starts an attempt, processes responses and finishes the attempt.
|
||||
*
|
||||
* @param $attemptoptions ['quiz'] => object, ['student'] => object, ['tosubmit'] => array, ['attemptnumber'] => int
|
||||
*/
|
||||
private function do_attempt_quiz($attemptoptions) {
|
||||
$quizobj = quiz::create($attemptoptions['quiz']->id);
|
||||
|
||||
// Start the passing attempt.
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $passstudent->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptoptions['attemptnumber'], false, $timenow, false,
|
||||
$attemptoptions['student']->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptoptions['attemptnumber'], $timenow);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
|
||||
// Process some responses from the student.
|
||||
// Process responses from the student.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$tosubmit = array(1 => array('answer' => '3.14'));
|
||||
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
|
||||
$attemptobj->process_submitted_actions($timenow, false, $attemptoptions['tosubmit']);
|
||||
|
||||
// Finish the attempt.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
|
||||
$attemptobj->process_finish($timenow, false);
|
||||
}
|
||||
|
||||
// Start the failing attempt.
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
/**
|
||||
* Test checking the completion state of a quiz.
|
||||
* The quiz requires a passing grade to be completed.
|
||||
*/
|
||||
public function test_quiz_get_completion_state_completionpass() {
|
||||
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $failstudent->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
|
||||
'nbstudents' => 2,
|
||||
'qtype' => 'numerical',
|
||||
'quizoptions' => [
|
||||
'completionusegrade' => 1,
|
||||
'completionpass' => 1
|
||||
]
|
||||
]);
|
||||
|
||||
// Process some responses from the student.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$tosubmit = array(1 => array('answer' => '0'));
|
||||
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
|
||||
list($passstudent, $failstudent) = $students;
|
||||
|
||||
// Finish the attempt.
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
|
||||
$attemptobj->process_finish($timenow, false);
|
||||
// Do a passing attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $passstudent,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => '3.14']]
|
||||
]);
|
||||
|
||||
// Check the results.
|
||||
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
|
||||
|
||||
// Do a failing attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $failstudent,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => '0']]
|
||||
]);
|
||||
|
||||
// Check the results.
|
||||
$this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test checking the completion state of a quiz.
|
||||
* To be completed, this quiz requires either a passing grade or for all attempts to be used up.
|
||||
*/
|
||||
public function test_quiz_get_completion_state_completionexhausted() {
|
||||
|
||||
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
|
||||
'nbstudents' => 2,
|
||||
'qtype' => 'numerical',
|
||||
'quizoptions' => [
|
||||
'attempts' => 2,
|
||||
'completionusegrade' => 1,
|
||||
'completionpass' => 1,
|
||||
'completionattemptsexhausted' => 1
|
||||
]
|
||||
]);
|
||||
|
||||
list($passstudent, $exhauststudent) = $students;
|
||||
|
||||
// Start a passing attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $passstudent,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => '3.14']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is completed by $passstudent because of passing grade.
|
||||
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
|
||||
|
||||
// Do a failing attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $exhauststudent,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => '0']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is not completed by $exhauststudent yet because of failing grade and of remaining attempts.
|
||||
$this->assertFalse(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
|
||||
|
||||
// Do a second failing attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $exhauststudent,
|
||||
'attemptnumber' => 2,
|
||||
'tosubmit' => [1 => ['answer' => '0']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is completed by $exhauststudent because there are no remaining attempts.
|
||||
$this->assertTrue(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test checking the completion state of a quiz.
|
||||
* To be completed, this quiz requires a minimum number of attempts.
|
||||
*/
|
||||
public function test_quiz_get_completion_state_completionminattempts() {
|
||||
|
||||
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
|
||||
'nbstudents' => 1,
|
||||
'qtype' => 'essay',
|
||||
'quizoptions' => [
|
||||
'completionminattemptsenabled' => 1,
|
||||
'completionminattempts' => 2
|
||||
]
|
||||
]);
|
||||
|
||||
list($student) = $students;
|
||||
|
||||
// Do a first attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $student,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is not completed yet because only one attempt was done.
|
||||
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
|
||||
|
||||
// Do a second attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $student,
|
||||
'attemptnumber' => 2,
|
||||
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is completed by $student because two attempts were done.
|
||||
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test checking the completion state of a quiz.
|
||||
* To be completed, this quiz requires a minimum number of attempts AND a passing grade.
|
||||
* This is somewhat of an edge case as it is hard to imagine a scenario in which these precise settings are useful.
|
||||
* Nevertheless, this test makes sure these settings interact as intended.
|
||||
*/
|
||||
public function test_quiz_get_completion_state_completionminattempts_pass() {
|
||||
|
||||
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
|
||||
'nbstudents' => 1,
|
||||
'qtype' => 'numerical',
|
||||
'quizoptions' => [
|
||||
'attempts' => 2,
|
||||
'completionusegrade' => 1,
|
||||
'completionpass' => 1,
|
||||
'completionminattemptsenabled' => 1,
|
||||
'completionminattempts' => 2
|
||||
]
|
||||
]);
|
||||
|
||||
list($student) = $students;
|
||||
|
||||
// Start a first attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $student,
|
||||
'attemptnumber' => 1,
|
||||
'tosubmit' => [1 => ['answer' => '3.14']]
|
||||
]);
|
||||
|
||||
// Check the results. Even though one requirement is met (passing grade) quiz is not completed yet because only
|
||||
// one attempt was done.
|
||||
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
|
||||
|
||||
// Start a second attempt.
|
||||
$this->do_attempt_quiz([
|
||||
'quiz' => $quiz,
|
||||
'student' => $student,
|
||||
'attemptnumber' => 2,
|
||||
'tosubmit' => [1 => ['answer' => '42']]
|
||||
]);
|
||||
|
||||
// Check the results. Quiz is completed by $student because two attempts were done AND a passing grade was obtained.
|
||||
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
|
||||
}
|
||||
|
||||
public function test_quiz_get_user_attempts() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2020061500;
|
||||
$plugin->version = 2020061501;
|
||||
$plugin->requires = 2020060900;
|
||||
$plugin->component = 'mod_quiz';
|
||||
|
Loading…
x
Reference in New Issue
Block a user