Merge branch 'MDL-53870-master' of git://github.com/jleyva/moodle
This commit is contained in:
@@ -1866,7 +1866,7 @@ class core_plugin_manager {
|
||||
),
|
||||
|
||||
'quizaccess' => array(
|
||||
'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
|
||||
'delaybetweenattempts', 'ipaddress', 'numattempts', 'offlineattempts', 'openclosedate',
|
||||
'password', 'safebrowser', 'securewindow', 'timelimit'
|
||||
),
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for the quizaccess_offlineattempts plugin.
|
||||
*
|
||||
* @package quizaccess_offlineattempts
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['allowofflineattempts'] = 'Allow quiz to be attempted offline in the mobile app';
|
||||
$string['allowofflineattempts_help'] = 'If checked, the user will be able to download the quiz to attempt it offline using the Mobile app. If the user download a quiz for offline, a new attempt will be created. This attempt will be empty until the user synchronize the results of the attempt in the Mobile app.';
|
||||
$string['confirmdatasaved'] = 'I confirm that I don’t have unsaved work on my mobile devices before continuing this attempt.';
|
||||
$string['mobileapp'] = 'Mobile app';
|
||||
$string['offlineattemptserror'] = 'Offline quizzes are not compatible with quizzes using timers, access restriction by password or subnet and quizzes using behaviours different than deferred feedback without or with CBM';
|
||||
$string['offlinedatamessage'] = 'You have worked on this attempt in a mobile device, and that data was last saved to this site {$a} ago.';
|
||||
$string['pleaseconfirm'] = 'Please, confirm that you don\'t have unsaved work on your devices';
|
||||
$string['pluginname'] = 'Offline attempts access rule';
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Implementaton of the quizaccess_offlineattempts plugin.
|
||||
*
|
||||
* @package quizaccess_offlineattempts
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
|
||||
|
||||
/**
|
||||
* A rule implementing the offlineattempts check.
|
||||
*
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 3.2
|
||||
*/
|
||||
class quizaccess_offlineattempts extends quiz_access_rule_base {
|
||||
|
||||
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
|
||||
global $CFG;
|
||||
|
||||
// If mobile services are off, the user won't be able to use any external app.
|
||||
if (empty($CFG->enablemobilewebservice) or empty($quizobj->get_quiz()->allowofflineattempts)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new self($quizobj, $timenow);
|
||||
}
|
||||
|
||||
public function is_preflight_check_required($attemptid) {
|
||||
global $SESSION, $DB;
|
||||
|
||||
// First, check if the user did something offline.
|
||||
if (!empty($attemptid)) {
|
||||
$timemodifiedoffline = $DB->get_field('quiz_attempts', 'timemodifiedoffline', array('id' => $attemptid));
|
||||
if (empty($timemodifiedoffline)) {
|
||||
return false;
|
||||
}
|
||||
return empty($SESSION->offlineattemptscheckedquizzes[$this->quiz->id]);
|
||||
} else {
|
||||
// Starting a new attempt, we don't have to check anything here.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function add_preflight_check_form_fields(mod_quiz_preflight_check_form $quizform,
|
||||
MoodleQuickForm $mform, $attemptid) {
|
||||
global $DB;
|
||||
|
||||
$timemodifiedoffline = $DB->get_field('quiz_attempts', 'timemodifiedoffline', array('id' => $attemptid));
|
||||
$lasttime = format_time(time() - $timemodifiedoffline);
|
||||
|
||||
$mform->addElement('header', 'offlineattemptsheader', get_string('mobileapp', 'quizaccess_offlineattempts'));
|
||||
$mform->addElement('static', 'offlinedatamessage', '',
|
||||
get_string('offlinedatamessage', 'quizaccess_offlineattempts', $lasttime));
|
||||
$mform->addElement('advcheckbox', 'confirmdatasaved', null,
|
||||
get_string('confirmdatasaved', 'quizaccess_offlineattempts'));
|
||||
}
|
||||
|
||||
public function validate_preflight_check($data, $files, $errors, $attemptid) {
|
||||
|
||||
// The user confirmed that he doesn't have unsaved work.
|
||||
if (!empty($data['confirmdatasaved'])) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$errors['confirmdatasaved'] = get_string('pleaseconfirm', 'quizaccess_offlineattempts');
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function notify_preflight_check_passed($attemptid) {
|
||||
global $SESSION;
|
||||
$SESSION->offlineattemptscheckedquizzes[$this->quiz->id] = true;
|
||||
}
|
||||
|
||||
public function current_attempt_finished() {
|
||||
global $SESSION;
|
||||
// Clear the flag in the session that says that the user has already agreed to the notice.
|
||||
if (!empty($SESSION->offlineattemptscheckedquizzes[$this->quiz->id])) {
|
||||
unset($SESSION->offlineattemptscheckedquizzes[$this->quiz->id]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function add_settings_form_fields(
|
||||
mod_quiz_mod_form $quizform, MoodleQuickForm $mform) {
|
||||
global $CFG;
|
||||
|
||||
// Allow to enable the access rule only if the Mobile services are enabled.
|
||||
if ($CFG->enablemobilewebservice) {
|
||||
$mform->addElement('selectyesno', 'allowofflineattempts',
|
||||
get_string('allowofflineattempts', 'quizaccess_offlineattempts'));
|
||||
$mform->addHelpButton('allowofflineattempts', 'allowofflineattempts', 'quizaccess_offlineattempts');
|
||||
$mform->setDefault('allowofflineattempts', 0);
|
||||
$mform->setAdvanced('allowofflineattempts');
|
||||
$mform->disabledIf('allowofflineattempts', 'timelimit[number]', 'neq', 0);
|
||||
$mform->disabledIf('allowofflineattempts', 'subnet', 'neq', '');
|
||||
}
|
||||
}
|
||||
|
||||
public static function validate_settings_form_fields(array $errors,
|
||||
array $data, $files, mod_quiz_mod_form $quizform) {
|
||||
global $CFG;
|
||||
|
||||
if ($CFG->enablemobilewebservice) {
|
||||
// Do not allow offline attempts if:
|
||||
// - The quiz uses a timer.
|
||||
// - The quiz is restricted by subnet.
|
||||
// - The question behaviour is not deferred feedback or deferred feedback with CBM.
|
||||
if (!empty($data['allowofflineattempts']) and
|
||||
(!empty($data['timelimit']) or !empty($data['subnet']) or
|
||||
($data['preferredbehaviour'] != 'deferredfeedback' and $data['preferredbehaviour'] != 'deferredcbm'))) {
|
||||
|
||||
$errors['allowofflineattempts'] = get_string('offlineattemptserror', 'quizaccess_offlineattempts');
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the quizaccess_offlineattempts plugin.
|
||||
*
|
||||
* @package quizaccess_offlineattempts
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/quiz/accessrule/offlineattempts/rule.php');
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for the quizaccess_offlineattempts plugin.
|
||||
*
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class quizaccess_offlineattempts_testcase extends basic_testcase {
|
||||
public function test_offlineattempts_access_rule() {
|
||||
$quiz = new stdClass();
|
||||
$quiz->allowofflineattempts = 1;
|
||||
$cm = new stdClass();
|
||||
$cm->id = 0;
|
||||
$quizobj = new quiz($quiz, $cm, null);
|
||||
$rule = new quizaccess_offlineattempts($quizobj, 0);
|
||||
$attempt = new stdClass();
|
||||
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Version information for the quizaccess_offlineattempts plugin.
|
||||
*
|
||||
* @package quizaccess_offlineattempts
|
||||
* @copyright 2016 Juan Leyva
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2016042600;
|
||||
$plugin->requires = 2016042200;
|
||||
$plugin->component = 'quizaccess_offlineattempts';
|
||||
@@ -2320,6 +2320,24 @@ class quiz_attempt {
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the timemodifiedoffline attempt field.
|
||||
* This function should be used only when web services are being used.
|
||||
*
|
||||
* @param int $time time stamp
|
||||
* @return boolean false if the field is not updated becase web services aren't being used.
|
||||
* @since Moodle 3.2
|
||||
*/
|
||||
public function set_offline_modified_time($time) {
|
||||
global $DB;
|
||||
|
||||
// Update the timemodifiedoffline field only if web services are being used.
|
||||
if (WS_SERVER) {
|
||||
$attemptobj->attempt->timemodifiedoffline = $time;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ 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', 'completionpass',
|
||||
'allowofflineattempts'));
|
||||
|
||||
// Define elements for access rule subplugin settings.
|
||||
$this->add_subplugin_structure('quizaccess', $quiz, true);
|
||||
@@ -84,7 +85,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
|
||||
|
||||
$attempt = new backup_nested_element('attempt', array('id'), array(
|
||||
'userid', 'attemptnum', 'uniqueid', 'layout', 'currentpage', 'preview',
|
||||
'state', 'timestart', 'timefinish', 'timemodified', 'timecheckstate', 'sumgrades'));
|
||||
'state', 'timestart', 'timefinish', 'timemodified', 'timemodifiedoffline', 'timecheckstate', 'sumgrades'));
|
||||
|
||||
// This module is using questions, so produce the related question states and sessions
|
||||
// attaching them to the $attempt element based in 'uniqueid' matching.
|
||||
|
||||
@@ -375,6 +375,7 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
|
||||
$data->timestart = $this->apply_date_offset($data->timestart);
|
||||
$data->timefinish = $this->apply_date_offset($data->timefinish);
|
||||
$data->timemodified = $this->apply_date_offset($data->timemodified);
|
||||
|
||||
if (!empty($data->timecheckstate)) {
|
||||
$data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
|
||||
} else {
|
||||
|
||||
@@ -131,7 +131,8 @@ class mod_quiz_external extends external_api {
|
||||
'reviewoverallfeedback', 'questionsperpage', 'navmethod', 'sumgrades', 'grade',
|
||||
'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks',
|
||||
'completionattemptsexhausted', 'completionpass', 'overduehandling',
|
||||
'graceperiod', 'preferredbehaviour', 'canredoquestions');
|
||||
'graceperiod', 'preferredbehaviour', 'canredoquestions',
|
||||
'allowofflineattempts');
|
||||
$viewablefields = array_merge($viewablefields, $additionalfields);
|
||||
}
|
||||
|
||||
@@ -258,6 +259,8 @@ class mod_quiz_external extends external_api {
|
||||
exhausted the maximum number of attempts',
|
||||
VALUE_OPTIONAL),
|
||||
'completionpass' => new external_value(PARAM_INT, 'Whether to require passing grade', VALUE_OPTIONAL),
|
||||
'allowofflineattempts' => new external_value(PARAM_INT, 'Whether to allow the quiz to be attempted
|
||||
offline in the mobile app', VALUE_OPTIONAL),
|
||||
'autosaveperiod' => new external_value(PARAM_INT, 'Auto-save delay', VALUE_OPTIONAL),
|
||||
'hasfeedback' => new external_value(PARAM_INT, 'Whether the quiz has any non-blank feedback text',
|
||||
VALUE_OPTIONAL),
|
||||
@@ -445,6 +448,7 @@ class mod_quiz_external extends external_api {
|
||||
'timefinish' => new external_value(PARAM_INT, 'Time when the attempt was submitted.
|
||||
0 if the attempt has not been submitted yet.', VALUE_OPTIONAL),
|
||||
'timemodified' => new external_value(PARAM_INT, 'Last modified time.', VALUE_OPTIONAL),
|
||||
'timemodifiedoffline' => new external_value(PARAM_INT, 'Last modified time via webservices.', VALUE_OPTIONAL),
|
||||
'timecheckstate' => new external_value(PARAM_INT, 'Next time quiz cron should check attempt for
|
||||
state changes. NULL means never check.', VALUE_OPTIONAL),
|
||||
'sumgrades' => new external_value(PARAM_FLOAT, 'Total marks for this attempt.', VALUE_OPTIONAL),
|
||||
@@ -748,7 +752,8 @@ class mod_quiz_external extends external_api {
|
||||
throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress');
|
||||
}
|
||||
}
|
||||
$attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt);
|
||||
$offlineattempt = WS_SERVER ? true : false;
|
||||
$attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt, $offlineattempt);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
@@ -872,6 +877,11 @@ class mod_quiz_external extends external_api {
|
||||
'type' => new external_value(PARAM_ALPHANUMEXT, 'question type, i.e: multichoice'),
|
||||
'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'),
|
||||
'html' => new external_value(PARAM_RAW, 'the question rendered'),
|
||||
'sequencecheck' => new external_value(PARAM_INT, 'the number of real steps in this attempt', VALUE_OPTIONAL),
|
||||
'lastactiontime' => new external_value(PARAM_INT, 'the timestamp of the most recent step in this question attempt',
|
||||
VALUE_OPTIONAL),
|
||||
'hasautosavedstep' => new external_value(PARAM_BOOL, 'whether this question attempt has autosaved data',
|
||||
VALUE_OPTIONAL),
|
||||
'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'),
|
||||
'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL),
|
||||
'state' => new external_value(PARAM_ALPHA, 'the state where the question is in', VALUE_OPTIONAL),
|
||||
@@ -907,7 +917,10 @@ class mod_quiz_external extends external_api {
|
||||
'type' => $attemptobj->get_question_type_name($slot),
|
||||
'page' => $attemptobj->get_question_page($slot),
|
||||
'flagged' => $attemptobj->is_question_flagged($slot),
|
||||
'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code()
|
||||
'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code(),
|
||||
'sequencecheck' => $attemptobj->get_question_attempt($slot)->get_sequence_check_count(),
|
||||
'lastactiontime' => $attemptobj->get_question_attempt($slot)->get_last_step()->get_timecreated(),
|
||||
'hasautosavedstep' => $attemptobj->get_question_attempt($slot)->has_autosaved_step()
|
||||
);
|
||||
|
||||
if ($attemptobj->is_real_question($slot)) {
|
||||
@@ -1136,6 +1149,8 @@ class mod_quiz_external extends external_api {
|
||||
$_POST[$element['name']] = $element['value'];
|
||||
}
|
||||
$timenow = time();
|
||||
// Update the timemodifiedoffline field.
|
||||
$attemptobj->set_offline_modified_time($timenow);
|
||||
$attemptobj->process_auto_save($timenow);
|
||||
$transaction->allow_commit();
|
||||
|
||||
@@ -1231,7 +1246,10 @@ class mod_quiz_external extends external_api {
|
||||
$timeup = $params['timeup'];
|
||||
|
||||
$result = array();
|
||||
// Update the timemodifiedoffline field.
|
||||
$attemptobj->set_offline_modified_time($timenow);
|
||||
$result['state'] = $attemptobj->process_attempt($timenow, $finishattempt, $timeup, 0);
|
||||
|
||||
$result['warnings'] = $warnings;
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -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="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>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@@ -136,6 +137,7 @@
|
||||
<FIELD NAME="timestart" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time when the attempt was started."/>
|
||||
<FIELD NAME="timefinish" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time when the attempt was submitted. 0 if the attempt has not been submitted yet."/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Last modified time."/>
|
||||
<FIELD NAME="timemodifiedoffline" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Last modified time via web services."/>
|
||||
<FIELD NAME="timecheckstate" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Next time quiz cron should check attempt for state changes. NULL means never check."/>
|
||||
<FIELD NAME="sumgrades" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Total marks for this attempt."/>
|
||||
</FIELDS>
|
||||
|
||||
@@ -193,5 +193,32 @@ function xmldb_quiz_upgrade($oldversion) {
|
||||
// Moodle v3.1.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2016092000) {
|
||||
// Define new fields to be added to quiz.
|
||||
$table = new xmldb_table('quiz');
|
||||
|
||||
$field = new xmldb_field('allowofflineattempts', XMLDB_TYPE_INTEGER, '1', null, null, null, 0, 'completionpass');
|
||||
// Conditionally launch add field allowofflineattempts.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2016092000, 'quiz');
|
||||
}
|
||||
|
||||
if ($oldversion < 2016092001) {
|
||||
// New field for quiz_attemps.
|
||||
$table = new xmldb_table('quiz_attempts');
|
||||
|
||||
$field = new xmldb_field('timemodifiedoffline', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0, 'timemodified');
|
||||
// Conditionally launch add field timemodifiedoffline.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Quiz savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2016092001, 'quiz');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timen
|
||||
$attempt->timestart = $timenow;
|
||||
$attempt->timefinish = 0;
|
||||
$attempt->timemodified = $timenow;
|
||||
$attempt->timemodifiedoffline = 0;
|
||||
$attempt->state = quiz_attempt::IN_PROGRESS;
|
||||
$attempt->currentpage = 0;
|
||||
$attempt->sumgrades = null;
|
||||
@@ -2259,10 +2260,11 @@ function quiz_validate_new_attempt(quiz $quizobj, quiz_access_manager $accessman
|
||||
* @param quiz $quizobj quiz object
|
||||
* @param int $attemptnumber the attempt number
|
||||
* @param object $lastattempt last attempt object
|
||||
* @param bool $offlineattempt whether is an offline attempt or not
|
||||
* @return object the new attempt
|
||||
* @since Moodle 3.1
|
||||
*/
|
||||
function quiz_prepare_and_start_new_attempt(quiz $quizobj, $attemptnumber, $lastattempt) {
|
||||
function quiz_prepare_and_start_new_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $offlineattempt = false) {
|
||||
global $DB, $USER;
|
||||
|
||||
// Delete any previous preview attempts belonging to this user.
|
||||
@@ -2283,6 +2285,10 @@ function quiz_prepare_and_start_new_attempt(quiz $quizobj, $attemptnumber, $last
|
||||
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
// Init the timemodifiedoffline for offline attempts.
|
||||
if ($offlineattempt) {
|
||||
$attempt->timemodifiedoffline = $attempt->timemodified;
|
||||
}
|
||||
$attempt = quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
|
||||
$transaction->allow_commit();
|
||||
|
||||
@@ -199,7 +199,8 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
'reviewoverallfeedback', 'questionsperpage', 'navmethod', 'sumgrades', 'grade',
|
||||
'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks',
|
||||
'completionattemptsexhausted', 'completionpass', 'autosaveperiod', 'hasquestions',
|
||||
'hasfeedback', 'overduehandling', 'graceperiod', 'preferredbehaviour', 'canredoquestions');
|
||||
'hasfeedback', 'overduehandling', 'graceperiod', 'preferredbehaviour', 'canredoquestions',
|
||||
'allowofflineattempts');
|
||||
$managerfields = array('shuffleanswers', 'timecreated', 'timemodified', 'password', 'subnet');
|
||||
|
||||
// Add expected coursemodule and other data.
|
||||
@@ -850,6 +851,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
public function test_get_attempt_data() {
|
||||
global $DB;
|
||||
|
||||
$timenow = time();
|
||||
// Create a new quiz with one attempt started.
|
||||
list($quiz, $context, $quizobj, $attempt, $attemptobj) = $this->create_quiz_with_questions(true);
|
||||
|
||||
@@ -877,6 +879,9 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals(0, $result['questions'][0]['page']);
|
||||
$this->assertEmpty($result['questions'][0]['mark']);
|
||||
$this->assertEquals(1, $result['questions'][0]['maxmark']);
|
||||
$this->assertEquals(1, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
|
||||
// Now try the last page.
|
||||
$result = mod_quiz_external::get_attempt_data($attempt->id, 1);
|
||||
@@ -893,6 +898,9 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals(get_string('notyetanswered', 'question'), $result['questions'][0]['status']);
|
||||
$this->assertFalse($result['questions'][0]['flagged']);
|
||||
$this->assertEquals(1, $result['questions'][0]['page']);
|
||||
$this->assertEquals(1, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
|
||||
// Finish previous attempt.
|
||||
$attemptobj->process_finish(time(), false);
|
||||
@@ -981,6 +989,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
*/
|
||||
public function test_get_attempt_summary() {
|
||||
|
||||
$timenow = time();
|
||||
// Create a new quiz with one attempt started.
|
||||
list($quiz, $context, $quizobj, $attempt, $attemptobj) = $this->create_quiz_with_questions(true);
|
||||
|
||||
@@ -997,6 +1006,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($result['questions'][1]['flagged']);
|
||||
$this->assertEmpty($result['questions'][0]['mark']);
|
||||
$this->assertEmpty($result['questions'][1]['mark']);
|
||||
$this->assertEquals(1, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertEquals(1, $result['questions'][1]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][1]['lastactiontime']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
$this->assertEquals(false, $result['questions'][1]['hasautosavedstep']);
|
||||
|
||||
// Submit a response for the first question.
|
||||
$tosubmit = array(1 => array('answer' => '3.14'));
|
||||
@@ -1013,6 +1028,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($result['questions'][1]['flagged']);
|
||||
$this->assertEmpty($result['questions'][0]['mark']);
|
||||
$this->assertEmpty($result['questions'][1]['mark']);
|
||||
$this->assertEquals(2, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertEquals(1, $result['questions'][1]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][1]['lastactiontime']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
$this->assertEquals(false, $result['questions'][1]['hasautosavedstep']);
|
||||
|
||||
}
|
||||
|
||||
@@ -1021,6 +1042,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
*/
|
||||
public function test_save_attempt() {
|
||||
|
||||
$timenow = time();
|
||||
// Create a new quiz with one attempt started.
|
||||
list($quiz, $context, $quizobj, $attempt, $attemptobj, $quba) = $this->create_quiz_with_questions(true);
|
||||
|
||||
@@ -1052,6 +1074,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($result['questions'][1]['flagged']);
|
||||
$this->assertEmpty($result['questions'][0]['mark']);
|
||||
$this->assertEmpty($result['questions'][1]['mark']);
|
||||
$this->assertEquals(1, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertEquals(1, $result['questions'][1]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][1]['lastactiontime']);
|
||||
$this->assertEquals(true, $result['questions'][0]['hasautosavedstep']);
|
||||
$this->assertEquals(false, $result['questions'][1]['hasautosavedstep']);
|
||||
|
||||
// Now, second slot.
|
||||
$prefix = $quba->get_field_prefix(2);
|
||||
@@ -1072,7 +1100,9 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
|
||||
// Check it's marked as completed only the first one.
|
||||
$this->assertEquals('complete', $result['questions'][0]['state']);
|
||||
$this->assertEquals(1, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertEquals('complete', $result['questions'][1]['state']);
|
||||
$this->assertEquals(1, $result['questions'][1]['sequencecheck']);
|
||||
|
||||
}
|
||||
|
||||
@@ -1082,6 +1112,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
public function test_process_attempt() {
|
||||
global $DB;
|
||||
|
||||
$timenow = time();
|
||||
// Create a new quiz with two questions and one attempt started.
|
||||
list($quiz, $context, $quizobj, $attempt, $attemptobj, $quba) = $this->create_quiz_with_questions(true);
|
||||
|
||||
@@ -1113,6 +1144,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($result['questions'][1]['flagged']);
|
||||
$this->assertEmpty($result['questions'][0]['mark']);
|
||||
$this->assertEmpty($result['questions'][1]['mark']);
|
||||
$this->assertEquals(2, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertEquals(2, $result['questions'][0]['sequencecheck']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertGreaterThanOrEqual($timenow, $result['questions'][0]['lastactiontime']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
$this->assertEquals(false, $result['questions'][0]['hasautosavedstep']);
|
||||
|
||||
// Now, second slot.
|
||||
$prefix = $quba->get_field_prefix(2);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2016052300;
|
||||
$plugin->version = 2016092001;
|
||||
$plugin->requires = 2016051900;
|
||||
$plugin->component = 'mod_quiz';
|
||||
$plugin->cron = 60;
|
||||
|
||||
Reference in New Issue
Block a user