diff --git a/attempt.php b/attempt.php
index 43800a7..1b28fe9 100755
--- a/attempt.php
+++ b/attempt.php
@@ -134,7 +134,7 @@
$userid = '-u' . $USER->id;
-$wifisettings = $DB->get_record('quizaccess_wifiresilience', array('quizid' => $attemptobj->get_quizid()));
+$wifisettings = get_config('quizaccess_wifiresilience');
$displaytecherrors = 0;
$displaynavdetails = 0;
diff --git a/rule.php b/rule.php
index 23356e7..237920d 100755
--- a/rule.php
+++ b/rule.php
@@ -8,164 +8,142 @@
//
// 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
+// 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 .
+// along with Moodle. If not, see .
/**
* Implementaton of the quizaccess_wifiresilience plugin.
- *
- * @package quizaccess_wifiresilience
+ *
+ * @package quizaccess_wifiresilience
* @copyright 2017 ETH Zurich (amr.hourani@let.ethz.ch)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @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');
-
+require_once ($CFG->dirroot . '/mod/quiz/accessrule/accessrulebase.php');
/**
* The access rule class implementation for the quizaccess_wifiresilience plugin.
- *
* A rule that hijacks the standard attempt.php page, and replaces it with
* different script which loads all the questions at once and then allows the
* student to keep working, even if the network connection is lost. However,
* if the network is working, responses are saved back to the server.
- *
- * @copyright 2014 The Open University
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_wifiresilience extends quiz_access_rule_base {
-
+
/** @var string the URL path to our replacement attempt script. */
const ATTEMPT_URL = '/mod/quiz/accessrule/wifiresilience/attempt.php';
/**
* Declare make function
- * @param quiz $quizobj An instance of the class quiz from attemptlib.php.
- * The quiz we will be controlling access to.
- * @param int $timenow $timenow The time to use as 'now'.
- * @param bool $canignoretimelimits Whether this user is exempt from time
- * limits (has_capability('mod/quiz:ignoretimelimits', ...)).
+ *
+ * @param quiz $quizobj
+ * An instance of the class quiz from attemptlib.php.
+ * The quiz we will be controlling access to.
+ * @param int $timenow
+ * $timenow The time to use as 'now'.
+ * @param bool $canignoretimelimits
+ * Whether this user is exempt from time
+ * limits (has_capability('mod/quiz:ignoretimelimits', ...)).
* @return object quiz_access_rule_base
*/
public static function make(quiz $quizobj, $timenow, $canignoretimelimits) {
-
- if (empty($quizobj->get_quiz()->wifiresilience_enabled)
- || !self::is_compatible_behaviour($quizobj->get_quiz()->preferredbehaviour)) {
+ if (empty($quizobj->get_quiz()->wifiresilience_enabled) ||
+ !self::is_compatible_behaviour($quizobj->get_quiz()->preferredbehaviour)) {
return null;
}
-
+
return new self($quizobj, $timenow);
}
/**
* Add settings form
- * @param mod_quiz_mod_form $quizform
- * @param MoodleQuickForm $mform
+ *
+ * @param mod_quiz_mod_form $quizform
+ * @param MoodleQuickForm $mform
*/
public static function add_settings_form_fields(mod_quiz_mod_form $quizform, MoodleQuickForm $mform) {
-
$quizid = $quizform->get_current()->id;
-
- if ($quizid) {
- global $DB;
- $config = $DB->get_record('quizaccess_wifiresilience', array('quizid' => $quizid));
- }
-
- if (!$quizid || !$config) {
- $config = get_config('quizaccess_wifiresilience');
- }
-
- $mform->addElement('header', 'wifiresilienceenabled',
- get_string('wifiresilienceenabled', 'quizaccess_wifiresilience'));
-
- $mform->addElement('selectyesno', 'wifiresilience_enabled',
- get_string('wifiresilienceenabled', 'quizaccess_wifiresilience'));
-
- $mform->addHelpButton('wifiresilience_enabled',
- 'wifiresilienceenabled', 'quizaccess_wifiresilience');
-
+
+ $config = get_config('quizaccess_wifiresilience');
+
+ $mform->addElement('header', 'wifiresilienceenabled', get_string('wifiresilienceenabled', 'quizaccess_wifiresilience'));
+
+ $mform->addElement('selectyesno', 'wifiresilience_enabled',
+ get_string('wifiresilienceenabled', 'quizaccess_wifiresilience'));
+
+ $mform->addHelpButton('wifiresilience_enabled', 'wifiresilienceenabled', 'quizaccess_wifiresilience');
+
$mform->setDefault('wifiresilience_enabled', !empty($config->defaultenabled));
-
- $mform->addElement('checkbox', 'wifiresilience_prechecks',
- get_string('prechecks', 'quizaccess_wifiresilience'));
-
- $mform->addHelpButton('wifiresilience_prechecks',
- 'prechecks', 'quizaccess_wifiresilience');
-
+
+ $mform->addElement('html', '
';
}
-
- $return .= html_writer::div(
- get_string('technicalinspection', 'quizaccess_wifiresilience'),
- 'alert alert-warning',
- array(
- 'id' => 'wifiresilience_tech_pre_checks_div',
- 'style' => 'display:none; text-align:left'));
-
+
+ $return .= html_writer::div(get_string('technicalinspection', 'quizaccess_wifiresilience'), 'alert alert-warning',
+ array('id' => 'wifiresilience_tech_pre_checks_div', 'style' => 'display:none; text-align:left'));
+
return $return;
}
/**
* Setup attempt page
- * @param moodle_page $page the page object to initialise.
+ *
+ * @param moodle_page $page
+ * the page object to initialise.
*/
public function setup_attempt_page($page) {
if ($page->pagetype == 'mod-quiz-attempt' || $page->pagetype == 'mod-quiz-summary') {
diff --git a/serviceworker.php b/serviceworker.php
index d15cb71..5063fb2 100755
--- a/serviceworker.php
+++ b/serviceworker.php
@@ -42,7 +42,7 @@
die;
}
-$wifisettings = $DB->get_record('quizaccess_wifiresilience', array('quizid' => $quizid));
+$wifisettings = get_config('quizaccess_wifiresilience');
$rev = md5('offline.html');
$precahcedfilesstr =
diff --git a/styles.css b/styles.css
index e85f367..e3b7279 100755
--- a/styles.css
+++ b/styles.css
@@ -322,5 +322,6 @@ textarea.inspectresponse {
#page-mod-quiz-attempt .submitbtns {
text-align: center !important;
+ display: block;
}
/* stylelint-enable declaration-no-important */
\ No newline at end of file
diff --git a/time.php b/time.php
new file mode 100755
index 0000000..31b8691
--- /dev/null
+++ b/time.php
@@ -0,0 +1,91 @@
+.
+
+/**
+ * This script processes ajax timer equests during the quiz.
+ *
+ * @package quizaccess_wifiresilience
+ * @copyright 2017 ETH Zurich (amr.hourani@let.ethz.ch)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('AJAX_SCRIPT', true);
+
+require_once(__DIR__ . '/../../../../config.php');
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
+// Remember the current time as the time any responses were submitted.
+// (so as to make sure students don't get penalized for slow processing on this page).
+$timenow = time();
+
+// Get submitted parameters.
+$attemptid = required_param('attempt', PARAM_INT);
+
+$transaction = $DB->start_delegated_transaction();
+$attemptobj = quiz_attempt::create($attemptid);
+
+// Check login.
+if (!isloggedin() || !confirm_sesskey()) {
+ echo json_encode(array('result' => 'lostsession'));
+ die;
+}
+require_login($attemptobj->get_course(), false, $attemptobj->get_cm());
+require_sesskey();
+
+// Check that this attempt belongs to this user.
+if ($attemptobj->get_userid() != $USER->id) {
+ throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
+}
+
+// Check capabilities.
+if (!$attemptobj->is_preview_user()) {
+ $attemptobj->require_capability('mod/quiz:attempt');
+}
+$options = $attemptobj->get_display_options(false);
+
+// If the attempt is already closed, send them to the review page.
+if ($attemptobj->is_finished()) {
+ throw new moodle_quiz_exception($attemptobj->get_quizobj(),
+ 'attemptalreadyclosed', null, $attemptobj->review_url());
+}
+
+$accessmanager = $attemptobj->get_quizobj()->get_access_manager(time());
+$endtime = $accessmanager->get_end_time($attemptobj);
+
+if ($endtime === false) {
+ $endtime = 0;
+}
+
+$timeleft = $attemptobj->get_time_left_display(time());
+
+if ($timeleft !== false) {
+ $ispreview = $attemptobj->is_preview();
+ $timerstartvalue = $timeleft;
+ if (!$ispreview) {
+ /*
+ Make sure the timer starts just above zero. If $timeleft was <= 0, then
+ this will just have the effect of causing the quiz to be submitted immediately.
+ */
+ $timerstartvalue = max($timerstartvalue, 1);
+ }
+} else {
+ $timerstartvalue = 0;
+}
+$result = array();
+$result['result'] = 'OK';
+$result['timerstartvalue'] = $timerstartvalue;
+$result['timelimit'] = $endtime;
+echo json_encode($result);
diff --git a/upload.php b/upload.php
index c404543..c7bc76c 100755
--- a/upload.php
+++ b/upload.php
@@ -41,30 +41,30 @@
if ($form->is_cancelled()) {
redirect($quizurl);
-
+
} else if ($fromform = $form->get_data()) {
-
+
// Process submission.
$title = get_string('uploadingresponsesfor', 'quizaccess_wifiresilience',
- format_string($quiz->name, true, array('context' => $context)));
+ format_string($quiz->name, true, array('context' => $context)));
$PAGE->navbar->add($title);
$PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($course->fullname);
-
+
$files = get_file_storage()->get_area_files(context_user::instance($USER->id)->id,
- 'user', 'draft', $fromform->responsefiles, 'id');
+ 'user', 'draft', $fromform->responsefiles, 'id');
$filesprocessed = 0;
-
+
$privatekey = null;
$privatekeystring = get_config('quizaccess_wifiresilience', 'privatekey');
if ($privatekeystring) {
$privatekey = openssl_get_privatekey($privatekeystring);
}
-
+
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
-
+
foreach ($files as $file) {
if ($file->get_filepath() !== '/') {
continue; // Should not happen due to form validation.
@@ -72,21 +72,21 @@
if ($file->is_external_file()) {
continue; // Should not happen due to form validation.
}
-
+
if ($file->is_directory()) {
continue; // Not interesting.
}
-
+
echo $OUTPUT->heading(get_string('processingfile', 'quizaccess_wifiresilience', s($file->get_filename())), 3);
-
+
$originalpost = null;
$originalrequest = null;
-
+
try {
// Some files are already encoded, so decode them just in case.
$originalcontent = $file->get_content();
$decodedcontent = urldecode($originalcontent);
-
+
// Decode, compare to original. If it does differ, original is encoded.
// If it doesn't differ, original isn't encoded.
if ($decodedcontent == $originalcontent) {
@@ -96,99 +96,99 @@
$datares = $originalcontent;
$OUTPUT->notification(get_string('fileurlencoded', 'quizaccess_wifiresilience'));
}
-
+
$data = json_decode($datares);
-
+
if (!$data) {
if (function_exists('json_last_error_msg')) {
throw new coding_exception(
- get_string('filejsondecode', 'quizaccess_wifiresilience', json_last_error_msg()));
+ get_string('filejsondecode', 'quizaccess_wifiresilience', json_last_error_msg()));
} else {
throw new coding_exception(
- get_string('filejsondecodeerror', 'quizaccess_wifiresilience', json_last_error()));
+ get_string('filejsondecodeerror', 'quizaccess_wifiresilience', json_last_error()));
}
}
if (!isset($data->responses)) {
throw new coding_exception(
- get_string('filenoresponses', 'quizaccess_wifiresilience'));
+ get_string('filenoresponses', 'quizaccess_wifiresilience'));
}
-
+
if (isset($data->iv) || isset($data->key)) {
if (!$privatekey) {
throw new coding_exception(
- get_string('filenodecryptionkey', 'quizaccess_wifiresilience'));
+ get_string('filenodecryptionkey', 'quizaccess_wifiresilience'));
}
-
+
$encryptedaeskey = base64_decode($data->key);
if (!$encryptedaeskey) {
throw new coding_exception(
- get_string('fileencryptedkeynobase64', 'quizaccess_wifiresilience'));
+ get_string('fileencryptedkeynobase64', 'quizaccess_wifiresilience'));
}
-
+
$encryptediv = base64_decode($data->iv);
if (!$encryptediv) {
throw new coding_exception(
- get_string('fileencryptedinitvaluenobase64', 'quizaccess_wifiresilience'));
+ get_string('fileencryptedinitvaluenobase64', 'quizaccess_wifiresilience'));
}
-
+
$aeskeystring = '';
if (!openssl_private_decrypt($encryptedaeskey, $aeskeystring, $privatekey)) {
throw new coding_exception(
- get_string('fileunabledecryptkey', 'quizaccess_wifiresilience', openssl_error_string()));
+ get_string('fileunabledecryptkey', 'quizaccess_wifiresilience', openssl_error_string()));
}
-
+
$ivstring = '';
if (!openssl_private_decrypt($encryptediv, $ivstring, $privatekey)) {
throw new coding_exception(
- get_string('fileunabledecryptkey', 'quizaccess_wifiresilience', openssl_error_string()));
+ get_string('fileunabledecryptkey', 'quizaccess_wifiresilience', openssl_error_string()));
}
-
+
$aeskey = base64_decode($aeskeystring);
if (!$aeskey) {
throw new coding_exception(
- get_string('filekeynobase64', 'quizaccess_wifiresilience'));
+ get_string('filekeynobase64', 'quizaccess_wifiresilience'));
}
-
+
$iv = base64_decode($ivstring);
if (!$iv) {
throw new coding_exception(
- get_string('fileinitvaluenobase64', 'quizaccess_wifiresilience'));
+ get_string('fileinitvaluenobase64', 'quizaccess_wifiresilience'));
}
-
+
$responses = openssl_decrypt($data->responses, 'AES-256-CBC', $aeskey, 0, $iv);
if (!$responses) {
throw new coding_exception(
- get_string('fileunabledecrypt', 'quizaccess_wifiresilience', openssl_error_string()));
+ get_string('fileunabledecrypt', 'quizaccess_wifiresilience', openssl_error_string()));
}
} else {
$responses = $data->responses;
}
-
+
$postdata = array();
parse_str($responses, $postdata);
-
+
if (isset($fromform->takeattemptfromjson)) {
if (!isset($data->attemptid)) {
throw new coding_exception(
- get_string('filenoattemptidupload', 'quizaccess_wifiresilience'));
+ get_string('filenoattemptidupload', 'quizaccess_wifiresilience'));
}
$postdata['attempt'] = $data->attemptid;
}
-
+
if (!isset($postdata['attempt'])) {
throw new coding_exception(
- get_string('filenoattemptid', 'quizaccess_wifiresilience'));
+ get_string('filenoattemptid', 'quizaccess_wifiresilience'));
}
-
+
echo html_writer::tag('textarea', s(var_export($postdata, true)), array('readonly' => 'readonly'));
-
+ //$postdata['attempt'] = 98;
// Load the attempt.
$attemptobj = quiz_attempt::create($postdata['attempt']);
if ($attemptobj->get_cmid() != $cmid) {
throw new coding_exception(
- get_string('filewrongquiz', 'quizaccess_wifiresilience'));
+ get_string('filewrongquiz', 'quizaccess_wifiresilience'));
}
-
+
// Process the uploaded data. (We have to do weird fakery with $_POST && $_REQUEST).
$timenow = time();
$postdata['sesskey'] = sesskey();
@@ -196,28 +196,28 @@
$_POST = $postdata;
$originalrequest = $_REQUEST;
$_REQUEST = $postdata;
-
+
// Process times correctly.
if ($fromform->submissiontime) {
switch ($fromform->submissiontime) {
/* From file last_change */
case 1:
if (!isset($postdata['last_change'])
- || $postdata['last_change'] == '0'
- || !$postdata['last_change']
- || $postdata['last_change'] == 'undefined') {
- $postdata['last_change'] = $timenow;
- $timenow = $timenow;
- } else {
- $date = new DateTime($postdata['last_change']);
- $timenow = $date->getTimestamp();
- }
- break;
- /* Now */
+ || $postdata['last_change'] == '0'
+ || !$postdata['last_change']
+ || $postdata['last_change'] == 'undefined') {
+ $postdata['last_change'] = $timenow;
+ $timenow = $timenow;
+ } else {
+ $date = new DateTime($postdata['last_change']);
+ $timenow = $date->getTimestamp();
+ }
+ break;
+ /* Now */
case 2:
$timenow = time();
break;
- /* Quiz Finishtime */
+ /* Quiz Finishtime */
case 3:
$duedate = 0;
if ($quiz->timelimit) {
@@ -232,217 +232,113 @@
default:
$timenow = time();
}
-
+
if (!isset($timenow)) {
$timenow = time();
}
- }
- if ($fromform->finishattempts) {
-
- // Only if final submission has happened - otherwise now time for uploaded responses.
- // Override $fromform->submissiontime.
-
- if (isset($fromform->usefinalsubmissiontime)
- && isset($postdata['final_submission_time'])
- && $postdata['final_submission_time'] != 0) {
- $timenow = $postdata['final_submission_time'];
- }
-
- if (isset($fromform->createasnewattempt) && isset($data->userid) ) {
-
- $quizobj = quiz::create($quiz->id, $data->userid);
- // Start the attempt.
- $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
- $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
-
- // Look for an existing attempt.
- $attempts = quiz_get_user_attempts($quizobj->get_quizid(), $data->userid, 'all', true);
- $lastattempt = end($attempts);
-
- // Get number for the next or unfinished attempt.
- if ($lastattempt) {
- $attemptnumber = $lastattempt->attempt + 1;
- } else {
- $lastattempt = false;
- $attemptnumber = 1;
- }
- $currentattemptid = null;
-
- $attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow, false, $data->userid);
- quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
-
- quiz_attempt_save_started($quizobj, $quba, $attempt);
-
- // Process some responses from the student.
- $attemptobj = quiz_attempt::create($attempt->id);
-
- $from = "[q";
- $to = ":";
-
- foreach ($postdata as $key => $one) {
- if (strpos($key, ':1_') !== false) {
- $firstpos = strpos($key, $from);
- $secondpos = strpos($key, $to);
- $qid = substr($key , $firstpos, $secondpos);
- break;
- }
- }
-
- foreach ($postdata as $key => $one) {
- if (strpos($key, $qid) !== false) {
- unset($postdata[$key]);
- $n = str_replace($qid, 'q' . $attempt->uniqueid, $key);
- $postdata[$n] = $one;
- }
- }
-
- $newslots = $pieces = explode(",", $postdata['slots']);
-
- foreach ($newslots as $slot) {
- $qa = $attemptobj->get_question_attempt($slot);
- foreach ($postdata as $key => $one) {
- if (strpos($key, $slot . '_:sequencecheck') !== false) {
- $postdata[$key] = $qa->get_sequence_check_count();
+ }
+ // Only if final submission has happened - otherwise now time for uploaded responses.
+ // Override $fromform->submissiontime.
+
+ if (isset($fromform->usefinalsubmissiontime)
+ && isset($postdata['final_submission_time'])
+ && $postdata['final_submission_time'] != 0) {
+ $timenow = $postdata['final_submission_time'];
}
- }
- }
-
- // Now arrange question shuffle/order in the database to match original _order.
- $originaluniqueid = str_replace('q', '', $qid);
- $originaluniqueid = $originaluniqueid * 1;
-
- // Now we need to take the original unique id from database (in case of restore/backup).
- $originalattemptrec = $DB->get_record('quiz_attempts', array('id' => $postdata['attempt']));
- $originaluniqueid = $originalattemptrec->uniqueid;
-
- echo 'Original Unique ID: ' . $originaluniqueid . ' | Current Unique ID: ' . $attempt->uniqueid . '';
-
- // Fix shuffle (_order) issue. New attempt will match old attempt in _order.
- $originalorders = $DB->get_records_sql(
- 'SELECT random() as rand, ' .
- 'qasd.id, quba.id AS qubaid, ' .
- 'qa.id AS questionattemptid, ' .
- 'qa.questionusageid, qa.slot, qa.questionid, ' .
- 'qas.id AS attemptstepid, ' .
- 'qas.sequencenumber, ' .
- 'qas.userid, ' .
- 'qasd.name, ' .
- 'qasd.value ' .
- 'FROM {question_usages} quba ' .
- 'LEFT JOIN {question_attempts} qa ON qa.questionusageid = quba.id ' .
- 'LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id ' .
- 'LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id ' .
- 'WHERE quba.id = :v1 ORDER BY qa.slot, qas.sequencenumber',
- array('v1' => $originaluniqueid));
-
- $currentorders = $DB->get_records_sql(
- 'SELECT random() as rand, ' .
- 'qasd.id, quba.id AS qubaid, ' .
- 'qa.id AS questionattemptid, ' .
- 'qa.questionusageid, qa.slot, qa.questionid, ' .
- 'qas.id AS attemptstepid, ' .
- 'qas.sequencenumber, ' .
- 'qas.userid, ' .
- 'qasd.name, ' .
- 'qasd.value ' .
- 'FROM {question_usages} quba ' .
- 'LEFT JOIN {question_attempts} qa ON qa.questionusageid = quba.id ' .
- 'LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id ' .
- 'LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id ' .
- 'WHERE quba.id = :v1 ORDER BY qa.slot, qas.sequencenumber',
- array('v1' => $attempt->uniqueid));
-
- var_export($originalorders, true);
- echo "";
- var_export($currentorders, true);
-
- if ($originalorders) {
- $qids = array();
- $choiceqids = array();
- $stemqids = array();
-
- foreach ($originalorders as $orgord) {
- if ($orgord->name == '_order') {
- $qids[$orgord->questionid] = $orgord->value;
- }
- if ($orgord->name == '_choiceorder') {
- $choiceqids[$orgord->questionid] = $orgord->value;
+
+ if (isset($fromform->createasnewattempt) && isset($data->userid) ) {
+
+ $quizobj = quiz::create($quiz->id, $data->userid);
+
+ // Look for an existing attempt.
+ $attempts = quiz_get_user_attempts($quizobj->get_quizid(), $data->userid, 'all', true);
+ $lastattempt = end($attempts);
+
+ // Get number for the next or unfinished attempt.
+ if ($lastattempt) {
+ $attemptnumber = $lastattempt->attempt + 1;
+ } else {
+ $lastattempt = false;
+ $attemptnumber = 1;
+ }
+ $currentattemptid = null;
+
+ $userid = $data->userid;
+
+ $ispreviewuser = has_capability('mod/quiz:preview', $quizobj->get_context(), $userid);
+ // Delete any previous preview attempts belonging to this user.
+ quiz_delete_previews($quizobj->get_quiz(), $userid);
+
+ $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
+ $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
+
+ // Create the new attempt and initialize the question sessions
+ $attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow, $ispreviewuser, $userid);
+
+ // Force 'build on last' to avoid messy shuffling issues.
+ $attempt = quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt);
+
+ $transaction = $DB->start_delegated_transaction();
+
+ $attempt = quiz_attempt_save_started($quizobj, $quba, $attempt);
+
+ $transaction->allow_commit();
+
+ // Update the emergency with the new attempt unique id.
+ $from = '[q';
+ $to = ':';
+
+ foreach ($postdata as $key => $one) {
+ if (strpos($key, ':1_') !== false) {
+ $firstpos = strpos($key, $from);
+ $secondpos = strpos($key, $to);
+ $qid = substr($key , $firstpos, $secondpos);
+ break;
+ }
+ }
+
+ foreach ($postdata as $key => $one) {
+ if (strpos($key, $qid) !== false) {
+ unset($postdata[$key]);
+ $n = str_replace($qid, 'q' . $attempt->uniqueid, $key);
+ if(strpos($key, '1_sub0') !== false){
+ $postdata[$n] = 1;
+ }else if(strpos($key, '1_sub1') !== false){
+ $postdata[$n] = 2;
+ } else if(strpos($key, '1_sub2') !== false){
+ $postdata[$n] = 3;
+
+ }else{
+ $postdata[$n] = $one;
+ }
+
+ }
+ }
+ $_POST = $postdata;
+
+ $attemptobj = quiz_attempt::create($attempt->id);
+
}
- if ($orgord->name == '_stemorder') {
- $stemqids[$orgord->questionid] = $orgord->value;
+
+ if ($fromform->finishattempts) {
+ $attemptobj->process_attempt($timenow, true, 0, 0); // Finish is ticked.
+ } else {
+ if (isset($fromform->countrealofflinetime) && isset($postdata['real_offline_time'])) {
+ $timenow = $timenow - $postdata['real_offline_time'];
+ }
+ $attemptobj->process_attempt($timenow, false, 0, 0); // In progress.
}
- }
-
- echo "";
- var_export($qids, true);
- echo "";
- var_export($choiceqids, true);
- echo "";
-
- foreach ($currentorders as $currord) {
- // Update all attempts steps userid for the current.
- $sql = 'update {question_attempt_steps} set userid = :v1 where questionattemptid = :v2';
- $DB->execute($sql, array('v1' => $data->userid, 'v2' => $currord->questionattemptid));
-
- // Now update records as per quesiton order.
- if ($currord->name == '_order') {
- echo "_ORDER: Current quesiton id: $currord->questionid ";
- $sql = 'update {question_attempt_step_data} set value = :v1 ' .
- 'where name = :v2 and attemptstepid = :v3';
-
- $DB->execute($sql, array(
- 'v1' => $qids[$currord->questionid],
- 'v2' => '_order',
- 'v3' => $currord->attemptstepid));
- }
- // Now update records as per quesiton choiceorder.
- if ($currord->name == '_choiceorder') {
- echo "CHOICE_ORDER: Current quesiton id: $currord->questionid ";
- $sql = 'update {question_attempt_step_data} set value = :v1 ' .
- 'where name = :v2 and attemptstepid = :v3';
-
- $DB->execute($sql, array(
- 'v1' => $choiceqids[$currord->questionid],
- 'v2' => '_choiceorder',
- 'v3' => $currord->attemptstepid));
- }
- // Now update records as per quesiton choiceorder.
- if ($currord->name == '_stemorder') {
- echo "STEM_ORDER: Current quesiton id: $currord->questionid ";
- $sql = 'update {question_attempt_step_data} set value = :v1 ' .
- 'where name = :v2 and attemptstepid = :v3';
-
- $DB->execute($sql, array(
- 'v1' => $stemqids[$currord->questionid],
- 'v2' => '_stemorder',
- 'v3' => $currord->attemptstepid));
- }
- }
- }
-
- $_POST = $postdata;
-
- // Finish the attempt.
- $attemptobj = quiz_attempt::create($attempt->id);
- }
- $attemptobj->process_finish($timenow, true);
- } else {
- if (isset($fromform->countrealofflinetime) && isset($postdata['real_offline_time'])) {
- $timenow = $timenow - $postdata['real_offline_time'];
- }
- $attemptobj->process_submitted_actions($timenow); // In progress.
- }
-
- $_POST = $originalpost;
- $originalpost = null;
- $_REQUEST = $originalrequest;
- $originalrequest = null;
-
- // Display a success message.
- echo $OUTPUT->notification(get_string('dataprocessedsuccessfully', 'quizaccess_wifiresilience',
- html_writer::link($attemptobj->review_url(), get_string('reviewthisattempt', 'quizaccess_wifiresilience'))),
- 'notifysuccess');
-
+
+ $_POST = $originalpost;
+ $originalpost = null;
+ $_REQUEST = $originalrequest;
+ $originalrequest = null;
+
+ // Display a success message.
+ echo $OUTPUT->notification(get_string('dataprocessedsuccessfully', 'quizaccess_wifiresilience',
+ html_writer::link($attemptobj->review_url(), get_string('reviewthisattempt', 'quizaccess_wifiresilience'))),
+ 'notifysuccess');
+
} catch (Exception $e) {
if ($originalpost !== null) {
$_POST = $originalpost;
@@ -462,21 +358,21 @@
if ($privatekey) {
openssl_pkey_free($privatekey);
}
-
+
echo $OUTPUT->confirm(get_string('processingcomplete', 'quizaccess_wifiresilience', 3),
- new single_button($PAGE->url, get_string('uploadmoreresponses', 'quizaccess_wifiresilience'), 'get'),
- new single_button($quizurl, get_string('backtothequiz', 'quizaccess_wifiresilience'), 'get'));
+ new single_button($PAGE->url, get_string('uploadmoreresponses', 'quizaccess_wifiresilience'), 'get'),
+ new single_button($quizurl, get_string('backtothequiz', 'quizaccess_wifiresilience'), 'get'));
echo $OUTPUT->footer();
-
+
} else {
// Show the form.
$title = get_string('uploadresponsesfor', 'quizaccess_wifiresilience',
- format_string($quiz->name, true, array('context' => $context)));
+ format_string($quiz->name, true, array('context' => $context)));
$PAGE->navbar->add($title);
$PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($course->fullname);
-
+
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$form->display();
diff --git a/version.php b/version.php
index f728a39..e1b1bf6 100755
--- a/version.php
+++ b/version.php
@@ -23,9 +23,9 @@
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2021102500;
+$plugin->version = 2022031000;
$plugin->requires = 2020110900;
$plugin->cron = 0;
$plugin->component = 'quizaccess_wifiresilience';
$plugin->maturity = MATURITY_STABLE;
-$plugin->release = '0.95 for Moodle 3.10+';
+$plugin->release = '0.96 for Moodle 3.10+';
diff --git a/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-debug.js b/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-debug.js
index 38b1a1f..26edb1c 100644
--- a/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-debug.js
+++ b/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-debug.js
@@ -121,6 +121,7 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
* @private
*/
AUTOSAVE_HANDLER: M.cfg.wwwroot + '/mod/quiz/accessrule/wifiresilience/sync.php',
+ TIMER_HANDLER: M.cfg.wwwroot + '/mod/quiz/accessrule/wifiresilience/time.php',
/**
* Prefix for the localStorage key.
@@ -389,6 +390,7 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
// Try to get questions status in localstorage.
M.quizaccess_wifiresilience.localforage.try_to_get_question_states();
+ setInterval(this.sweep_for_timer_changes, this.delay + 5000);
var examviewportmaxwidth = $(window).width();
var quizaccess_wifiresilience_progress = $(".quizaccess_wifiresilience_progress .quizaccess_wifiresilience_bar");
@@ -568,6 +570,7 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
* @param {Number} repeatcount The number of attempts made so far.
*/
init_tinymce: function(repeatcount) {
+
if (typeof tinyMCE === 'undefined') {
if (repeatcount > 0) {
Y.later(this.TINYMCE_DETECTION_DELAY, this, this.init_tinymce, [repeatcount - 1]);
@@ -580,6 +583,7 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
Y.log('Found TinyMCE.', 'debug', '[Wifiresilience-SW] Sync');
this.editor_change_handler = Y.bind(this.editor_changed, this);
tinyMCE.onAddEditor.add(Y.bind(this.init_tinymce_editor, this));
+
},
/**
@@ -791,7 +795,64 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
this.set_question_state_class(slot, state);
}, this);
},
+ sweep_for_timer_changes: function(){
+
+ Y.log('** Intensive Timer changes checks.',
+ 'debug', '[Wifiresilience-SW] Sync');
+
+ // M.quizaccess_wifiresilience.autosave.request_timer_checks();
+ Y.io(M.quizaccess_wifiresilience.autosave.TIMER_HANDLER, {
+ method: 'POST',
+ form: {
+ id: M.quizaccess_wifiresilience.autosave.form
+ },
+ on: {
+ success: M.quizaccess_wifiresilience.autosave.timer_submit_done,
+ },
+ context: M.quizaccess_wifiresilience.autosave,
+ timeout: M.quizaccess_wifiresilience.autosave.SAVE_TIMEOUT_FULL_SUBMISSION,
+ sync: false
+ });
+ },
+ timer_submit_done: function(transactionid, response) {
+ var result;
+ try {
+ result = Y.JSON.parse(response.responseText);
+ } catch (e) {
+ return;
+ }
+ if (result.result === 'lostsession') {
+ Y.log('Session loss detected. Re-Login required', 'debug', '[Wifiresilience-SW] Sync');
+ return;
+ }
+
+ if (result.result !== 'OK') {
+ if (result.error) {
+ var sync_errors = result.error + ' (Code: ' + result.errorcode + ') Info: ' + result.debuginfo;
+ Y.log('Error: ' + sync_errors, 'debug', '[Wifiresilience-SW] Sync');
+ }
+ return;
+ }
+ // Is there a change happened to quiz end time? If so, then compensate it.
+ var original_end_time = Y.one('#original_end_time').get('value');
+ if (!isNaN(result.timerstartvalue) && M.mod_quiz.timer.endtime && original_end_time != result.timelimit) {
+ var addexloadingtime = 0;
+ if (exam_extra_page_load_time && exam_extra_page_load_time != 'undefined') {
+ addexloadingtime = exam_extra_page_load_time;
+ }
+ Y.log('Page load comepnsation autosave (exam end): ' + addexloadingtime, 'debug', '[Wifiresilience-SW] Sync');
+ var t = new Date().getTime();
+ M.mod_quiz.timer.endtime = exam_extra_page_load_time + t + result.timerstartvalue * 1000;
+ Y.log('Exam End Time checks on server: SUCCESS. Exam End Time: ' +
+ result.timerstartvalue, 'debug', '[Wifiresilience-SW] Sync');
+ } else {
+ Y.log('Exam End Time checks on server: Original Exam End Time: ' +
+ original_end_time + ' & Last Server Check End Time: ' +
+ result.timelimit + '. No Need to Compensate or Update Quiz Timer.' , 'debug', '[Wifiresilience-SW] Sync');
+ }
+ },
+
start_save_timer_if_necessary: function() {
this.dirty = true;
@@ -803,7 +864,7 @@ YUI.add('moodle-quizaccess_wifiresilience-autosave', function (Y, NAME) {
M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data);
this.exam_localstorage_saving_status_str();
-
+
if (this.delay_timer || this.save_transaction) {
return;
}
diff --git a/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-min.js b/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-min.js
index a20e053..26edb1c 100644
--- a/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-min.js
+++ b/yui/build/moodle-quizaccess_wifiresilience-autosave/moodle-quizaccess_wifiresilience-autosave-min.js
@@ -1 +1,1421 @@
-YUI.add("moodle-quizaccess_wifiresilience-autosave",(function(Y,NAME){M.quizaccess_wifiresilience=M.quizaccess_wifiresilience||{},M.quizaccess_wifiresilience.autosave={TINYMCE_DETECTION_DELAY:1e3,TINYMCE_DETECTION_REPEATS:10,WATCH_HIDDEN_DELAY:1e3,SAVE_TIMEOUT:3e4,SAVE_TIMEOUT_FULL_SUBMISSION:12e4,SELECTORS:{QUIZ_FORM:"#responseform",VALUE_CHANGE_ELEMENTS:'input, textarea, [contenteditable="true"]',CHANGE_ELEMENTS:"input, select",CONNECTION_ELEMENTS:"#quizaccess_wifiresilience_hidden_cxn_status",CHANGE_DRAGS:"li.matchdrag",HIDDEN_INPUTS:"input[type=hidden]",NAV_BUTTON:"#quiznavbutton",QUESTION_CONTAINER:"#q",STATE_HOLDER:" .state",SUMMARY_ROW:".quizsummaryofattempt tr.quizsummary",STATE_COLUMN:" .c1",ATTEMPT_ID_INPUT:"input[name=attempt]",FINISH_ATTEMPT_INPUT:"input[name=finishattempt]",SUBMIT_BUTTON:"#wifi_exam_submission_finish",FORM:"form",SAVING_NOTICE:"#quiz-saving",LAST_SAVED_MESSAGE:"#quiz-last-saved-message",LAST_SAVED_TIME:"#quiz-last-saved",SAVE_FAILED_NOTICE:"#mod_quiz_navblock .quiz-save-failed",LIVE_STATUS_AREA:"#quiz-server-status"},AUTOSAVE_HANDLER:M.cfg.wwwroot+"/mod/quiz/accessrule/wifiresilience/sync.php",LOCAL_STORAGE_KEY_PREFIX:"Wifiresilience-exams-responses-",RELOGIN_SCRIPT:M.cfg.wwwroot+"/mod/quiz/accessrule/wifiresilience/relogin.php",delay:2e4,total_offline_time:0,real_offline_time:0,last_disconnection_time:0,last_real_disconnection_time:0,keyname:null,courseid:null,cmid:null,cid:null,userid:null,attemptid:null,display_tech_errors:0,display_nav_details:0,disconnection_events:[],final_submission_time:0,form:null,connected:null,livewatch:null,serviceworker_supported:null,offline_happened_on:0,real_offline_happened_on:0,dirty:!1,delay_timer:null,usageid:null,save_transaction:null,save_start_time:null,last_successful_save:null,last_successful_server_save_timestamp:0,editor_change_handler:null,hidden_field_values:{},local_storage_key:null,sync_string_errors:"",locally_stored_data:{last_change:0,last_save:0,final_submission_time:0,userid:null,real_offline_time:0,total_offline_time:0,cid:null,cmid:null,attemptid:null,responses:""},init:function(delay,keyname,courseid,cmid,display_tech_errors,display_nav_details,usageid){if(this.form=Y.one(this.SELECTORS.QUIZ_FORM),this.form){quizaccess_wifiresilience_progress_step=5,$("#quizaccess_wifiresilience_result").html(M.util.get_string("loadingstep5","quizaccess_wifiresilience")),this.connected=!0,this.livewatch=!0,this.total_offline_time=0,this.real_offline_time=0,this.display_tech_errors=display_tech_errors,this.display_nav_details=display_nav_details,this.sync_string_errors="",this.attemptid=Y.one(this.SELECTORS.ATTEMPT_ID_INPUT).get("value"),this.usageid=usageid,"undefined"!=typeof Storage&&sessionStorage.getItem("real-offline-time-"+this.attemptid)&&(this.real_offline_time=Number(sessionStorage.getItem("real-offline-time-"+this.attemptid))),this.last_disconnection_time=0,this.last_real_disconnection_time=0,M.quizaccess_wifiresilience.autosave.offline_happened_on=0,M.quizaccess_wifiresilience.autosave.real_offline_happened_on=0,M.core_question_engine.init_form(Y,this.SELECTORS.QUIZ_FORM),Y.on("submit",M.mod_quiz.timer.stop,this.SELECTORS.QUIZ_FORM),window.onbeforeunload=Y.bind(this.warn_if_unsaved_data,this),this.delay=1e3*delay,this.local_storage_key=keyname,this.courseid=courseid,this.cmid=cmid,this.userid=Y.one("#quiz-userid").get("value"),storeddata=M.quizaccess_wifiresilience.localforage.get_status_records(),this.form.delegate("valuechange",this.value_changed,this.SELECTORS.VALUE_CHANGE_ELEMENTS,this),this.form.delegate("change",this.value_changed,this.SELECTORS.CHANGE_ELEMENTS,this);var submitAndFinishButton=Y.one(this.SELECTORS.SUBMIT_BUTTON);submitAndFinishButton.detach("click"),submitAndFinishButton.on("click",this.submit_and_finish_clicked,this),Y.DD.DDM.on("drag:drophit",this.value_changed_drag,this),Y.DD.DDM.on("drag:dropmiss",this.value_changed_drag,this),this.create_status_messages(),this.init_tinymce(this.TINYMCE_DETECTION_REPEATS),this.save_hidden_field_values(),this.watch_hidden_fields(),$("textarea").attr("autocomplete",!1),$("textarea").attr("autocorrect",!1),$("textarea").attr("autocapitalize",!1),$("textarea").attr("spellcheck",!1),"serviceWorker"in navigator?this.serviceworker_supported=1:this.serviceworker_supported=0,M.quizaccess_wifiresilience.localforage.try_to_get_question_states();var examviewportmaxwidth=$(window).width(),quizaccess_wifiresilience_progress;$(".quizaccess_wifiresilience_progress .quizaccess_wifiresilience_bar").animate({width:5*examviewportmaxwidth/10+"px"})}else Y.log("No response form found. Why did you try to set up autosave?","debug","[Wifiresilience-SW] Sync")},try_to_use_locally_saved_responses:function(){for(var prefill,alreadyusedparams=[],hashParams=this.locally_stored_data.responses.split("&"),reloaded_form_str="Loading (From localStorage)\n",i=0;i1&&alreadyusedparams.indexOf(name)>-1){var $eltemp=$('[name="'+name+'"]')[1];0==$eltemp.length&&($eltemp=$('[id="'+name+'"]')[1]);var type=($el=$($eltemp)).attr("type");console.log("DUPLICATE FIELD: More than one element hold same name/id. Now we are selecting: ",$el)}else var type=$el.attr("type");if(alreadyusedparams.push(name),-1!==name.indexOf("_sub")&&$('ul[name^="'+name+'"]').length&&$('ul[name^="'+name+'"]').attr("id")&&$('ul[name^="'+name+'"]').attr("id").length){var elementid=$('ul[name^="'+name+'"]').attr("id"),fullsepid,fullsepids=elementid.replace("ultarget","").split("_"),qid=fullsepids[0],stemid=fullsepids[1],target_value=$("#ulorigin"+qid).find("[data-id='"+val+"']");if(target_value&&target_value.length){var target_value_html=target_value.html();target_value_html&&"undefined"!=target_value_html&&($("#"+elementid).find(".placeholder").addClass("hidden"),$("#"+elementid).append('
'+target_value_html+"
"))}}switch(type){case"checkbox":var $el;if(0==($el=$("input[type='checkbox'][name='"+name+"']")).length)var $el=$("input[type='checkbox'][id='"+name+"']");0==val?$el.prop("checked",!1).change():$el.attr("checked","checked").change();var checkboxid=$el.attr("id");checkboxid&&"undefined"!=checkboxid&&-1!==checkboxid.indexOf("_distractor")&&($el.trigger("click"),$el.trigger("change"),$el.trigger("click"),$el.val(1));break;case"radio":$el.filter('[value="'+val+'"]').attr("checked","checked").change(),$el.filter('[value="'+val+'"]').trigger("click"),$el.trigger("change");break;default:if($el.is("textarea")){$el.text(val).change();var currentattrid=$el.attr("id");if(-1!==currentattrid.indexOf("qtype_drawing_textarea_id_")){var qidarr=currentattrid.split("_"),iframedr="#qtype_drawing_editor_"+qidarr[4]+"_"+qidarr[5]+"_"+qidarr[6]+"_uniqueuattemptid";$(iframedr).attr("src",$(iframedr).attr("src"))}else $el.val(val).change()}else{var $el;if($el.is("select")&&$("[name='"+name+"'] option[value='"+val+"']").attr("selected","selected"),"undefined"!=type)if(0==($el=$("input[type='"+type+"'][name='"+name+"']")).length)var $el=$("input[type='"+type+"'][id='"+name+"']");$el.val(val).change()}}}}Y.log("[LOCALSTORAGE]: Exam Reloaded before Saving. "+reloaded_form_str,"debug","[Wifiresilience-SW] Sync")},save_hidden_field_values:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each((function(hidden){var name=hidden.get("name");name&&(this.hidden_field_values[name]=hidden.get("value"))}),this)},watch_hidden_fields:function(){this.detect_hidden_field_changes(),Y.later(this.WATCH_HIDDEN_DELAY,this,this.watch_hidden_fields)},detect_hidden_field_changes:function(){this.form.all(this.SELECTORS.HIDDEN_INPUTS).each((function(hidden){var name=hidden.get("name"),value=hidden.get("value");name&&"sesskey"!==name&&(name in this.hidden_field_values&&value===this.hidden_field_values[name]||(this.hidden_field_values[name]=value,this.value_changed({target:hidden})))}),this)},init_tinymce:function(repeatcount){"undefined"!=typeof tinyMCE?(Y.log("Found TinyMCE.","debug","[Wifiresilience-SW] Sync"),this.editor_change_handler=Y.bind(this.editor_changed,this),tinyMCE.onAddEditor.add(Y.bind(this.init_tinymce_editor,this))):repeatcount>0?Y.later(this.TINYMCE_DETECTION_DELAY,this,this.init_tinymce,[repeatcount-1]):Y.log("Gave up looking for old dirty TinyMCE.","debug","[Wifiresilience-SW] Sync")},init_tinymce_editor:function(e,editor){Y.log("Found TinyMCE editor "+editor.id+".","debug","[Wifiresilience-SW] Sync"),editor.onChange.add(this.editor_change_handler),editor.onRedo.add(this.editor_change_handler),editor.onUndo.add(this.editor_change_handler),editor.onKeyDown.add(this.editor_change_handler)},value_changed_drag:function(e){if(void 0!==e.drop){var name=e.drop.get("node").getData("selectname");Y.log("Detected a value change in DRAG question.","debug","[Wifiresilience-SW] Sync"),this.start_save_timer_if_necessary(),this.mark_question_changed_if_necessary(name)}},connection_changed:function(e){Y.log("Detected change in Connection Status to "+this.connected,"debug","[Wifiresilience-SW] Timer");var total_seconds_missing=0;0!=this.offline_happened_on&&(total_seconds_missing=Math.floor(((new Date).getTime()-this.offline_happened_on)/1e3),this.total_offline_time+=total_seconds_missing,this.disconnection_events.push(total_seconds_missing),this.last_disconnection_time=total_seconds_missing,this.offline_happened_on=0,this.last_disconnection_time=0),this.connected?(this.offline_happened_on=0,this.last_disconnection_time=0,0!=total_seconds_missing&&Y.log("Total disconnection seconds (will not be compensated because the user is able to continue the exam): ["+total_seconds_missing+"] Seconds","debug","[Wifiresilience-SW] Timer")):this.offline_happened_on=(new Date).getTime()},real_connection_icon_status:function(status){Y.log("Is Exam Server up and running?! "+status,"debug","[Wifiresilience-SW] Server Status");var el=document.querySelector("#quizaccess_wifiresilience_connection"),cxn_hidden;0==document.querySelector("#quizaccess_wifiresilience_hidden_cxn_status").value&&(status=!1,Y.log("Device is not connected to internet - Ignore Server Status and mark it as offline: "+status,"debug","[Wifiresilience-SW] Server Status")),status?el.classList?(el.classList.add("connected"),el.classList.remove("disconnected")):(el.addClass("connected"),el.removeClass("disconnected")):el.classList?(el.classList.remove("connected"),el.classList.add("disconnected")):(el.removeClass("connected"),el.addClass("disconnected"))},livewatch_connection_changed:function(e){if(Y.log("Detected Livewatch: "+this.livewatch,"debug","[Wifiresilience-SW] Timer"),this.livewatch){var total_real_seconds_missing=0;0!=this.real_offline_happened_on&&(total_real_seconds_missing=Math.floor(((new Date).getTime()-this.real_offline_happened_on)/1e3),this.real_offline_time+=total_real_seconds_missing,this.last_real_disconnection_time=total_real_seconds_missing),this.real_offline_happened_on=0,M.mod_quiz.timer.update(e),this.last_real_disconnection_time=0,this.real_connection_icon_status(!0),Y.log("Total REAL missing seconds which got compensated:["+total_real_seconds_missing+"] Seconds","debug","[Wifiresilience-SW] Timer")}else M.mod_quiz.timer.stop(e),this.real_offline_happened_on=(new Date).getTime(),this.real_connection_icon_status(!1)},value_changed:function(e){var name=e.target.getAttribute("name");"thispage"===name||"scrollpos"===name||name&&name.match(/_:flagged$/)||("quizaccess_wifiresilience_cxn_status"!==name?"quizaccess_wifiresilience_livewatch_status"!==name?(name=name||"#"+e.target.getAttribute("id"),Y.log("Detected a value change in element "+name+".","debug","[Wifiresilience-SW] Sync"),this.start_save_timer_if_necessary(),this.mark_question_changed_if_necessary(name)):this.livewatch_connection_changed(e):this.connection_changed(e))},editor_changed:function(editor){Y.log("Detected a value change in editor "+editor.id+".","debug","[Wifiresilience-SW] Sync"),this.start_save_timer_if_necessary(),this.mark_question_changed_if_necessary(editor.id)},mark_question_changed_if_necessary:function(elementname){var slot=this.get_slot_from_id(elementname);slot&&(this.set_question_state_string(slot,M.util.get_string("answerchanged","quizaccess_wifiresilience")),this.set_question_state_class(slot,"answersaved"),M.quizaccess_wifiresilience.localforage.update_question_state_class(slot,"answersaved"),M.quizaccess_wifiresilience.localforage.update_question_state_string(slot,M.util.get_string("answerchanged","quizaccess_wifiresilience")))},get_slot_from_id:function(elementname){var matches=elementname.match(/^#?q\d+:(\d+)_.*$/),momo;return matches?matches[1]:$('input[name="'+elementname+'"]').closest("[id*=quizaccess_wifiresilience-attempt_page-]").attr("data-qslot")},set_question_state_string:function(slot,newstate){Y.log("State of question "+slot+" changed to "+newstate+".","debug","[Wifiresilience-SW] Sync"),Y.one(this.SELECTORS.QUESTION_CONTAINER+slot)?Y.one(this.SELECTORS.QUESTION_CONTAINER+slot+this.SELECTORS.STATE_HOLDER).setHTML(Y.Escape.html(newstate)):Y.one('div[id="question-'+this.usageid+"-"+slot+'"]'+this.SELECTORS.STATE_HOLDER).setHTML(Y.Escape.html(newstate));var summaryRow=Y.one(this.SELECTORS.SUMMARY_ROW+slot+this.SELECTORS.STATE_COLUMN);summaryRow&&summaryRow.setHTML(Y.Escape.html(newstate)),Y.one(this.SELECTORS.NAV_BUTTON+slot).set("title",Y.Escape.html(newstate))},update_question_state_strings:function(statestrings){Y.Object.each(statestrings,(function(state,slot){this.set_question_state_string(slot,state)}),this)},set_question_state_class:function(slot,newstate){Y.log("State of question "+slot+" changed to "+newstate+".","debug","[Wifiresilience-SW] Sync");var navButton=Y.one(this.SELECTORS.NAV_BUTTON+slot);navButton.set("className",navButton.get("className").replace(/^qnbutton \w+\b/,"qnbutton "+Y.Escape.html(newstate)))},update_question_state_classes:function(stateclasses){Y.Object.each(stateclasses,(function(state,slot){this.set_question_state_class(slot,state)}),this)},start_save_timer_if_necessary:function(){this.dirty=!0,this.locally_stored_data.last_change=new Date,this.locally_stored_data.responses=Y.IO.stringify(this.form);var stringified_data=Y.JSON.stringify(this.locally_stored_data);M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data),this.exam_localstorage_saving_status_str(),this.delay_timer||this.save_transaction||(Y.log("Changes worth syncing? Yes.. Flag Syncing to run after: "+this.delay+" milliseconds.","debug","[Wifiresilience-SW] Sync"),this.start_save_timer())},start_save_timer:function(){this.cancel_delay(),this.delay_timer=Y.later(this.delay,this,this.save_changes)},cancel_delay:function(){this.delay_timer&&!0!==this.delay_timer&&this.delay_timer.cancel(),this.delay_timer=null},get_live_exam_time:function(){Y.log("Trying to get Exam End Time in case of changes","debug","[Wifiresilience-SW] Sync")},exam_time_done:function(transactionid,response){Y.log("Exam End Time checks on server: SUCCESS. Exam End Time: "+timeresult.duedate,"debug","[Wifiresilience-SW] Sync")},exam_time_failed:function(transactionid,response){Y.log("Exam End Time checks on server: FAILED. "+response.responseText,"debug","[Wifiresilience-SW] Sync")},save_changes:function(){this.cancel_delay(),this.dirty=!1,this.locally_stored_data.responses=Y.IO.stringify(this.form);var stringified_data=Y.JSON.stringify(this.locally_stored_data);if(M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data),Y.log("Saving Exam Elements Status in indexedDB.","debug","[Wifiresilience-SW] Sync"),M.quizaccess_wifiresilience.localforage.save_attempt_records_encrypted(),Y.log("Saving Exam Encrypted Emergency File in indexedDB.","debug","[Wifiresilience-SW] Sync"),this.is_time_nearly_over())return Y.log("No more saving, time is nearly over.","debug","[Wifiresilience-SW] Sync"),void this.stop_autosaving();Y.log("Start Syncing with Exam Server.","debug","[Wifiresilience-SW] Sync"),"undefined"!=typeof tinyMCE&&tinyMCE.triggerSave(),this.save_transaction=Y.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{success:this.save_done,failure:this.save_failed},context:this,timeout:this.SAVE_TIMEOUT,sync:!1}),this.save_start_time=new Date,1==this.display_nav_details&&Y.one(this.SELECTORS.SAVING_NOTICE).setStyle("display","block")},save_done:function(transactionid,response){var result;try{result=Y.JSON.parse(response.responseText)}catch(e){return void this.save_failed(transactionid,response)}if("lostsession"===result.result)return Y.log("Session loss detected. Re-Login required","debug","[Wifiresilience-SW] Sync"),this.save_transaction=null,this.dirty=!0,void this.try_to_restore_session();if("OK"===result.result){this.sync_string_errors="",this.last_successful_server_save_timestamp=new Date,this.save_transaction=null;var original_end_time=Y.one("#original_end_time").get("value");if(!isNaN(result.timerstartvalue)&&M.mod_quiz.timer.endtime&&original_end_time!=result.timelimit){var addexloadingtime=0;exam_extra_page_load_time&&"undefined"!=exam_extra_page_load_time&&(addexloadingtime=exam_extra_page_load_time),Y.log("Page load comepnsation autosave (exam end): "+addexloadingtime,"debug","[Wifiresilience-SW] Sync");var t=(new Date).getTime();M.mod_quiz.timer.endtime=exam_extra_page_load_time+t+1e3*result.timerstartvalue,Y.log("Exam End Time checks on server: SUCCESS. Exam End Time: "+result.timerstartvalue,"debug","[Wifiresilience-SW] Sync")}else Y.log("Exam End Time checks on server: Original Exam End Time: "+original_end_time+" & Last Server Check End Time: "+result.timelimit+". No Need to Compensate or Update Quiz Timer.","debug","[Wifiresilience-SW] Sync");this.real_connection_icon_status(!0),this.update_status_for_successful_save(),this.update_question_state_classes(result.questionstates),this.update_question_state_strings(result.questionstatestrs),Y.log("[SUCCESSFUL] Full Sync to server completed.","debug","[Wifiresilience-SW] Sync"),this.real_connection_icon_status(!0),M.quizaccess_wifiresilience.localforage.save_question_state_classes(result.questionstates),M.quizaccess_wifiresilience.localforage.save_question_state_strings(result.questionstatestrs),this.dirty&&(Y.log("Dirty after syncing. Need to re-sync again.","debug","[Wifiresilience-SW] Sync"),this.start_save_timer())}else{if(result.error){var sync_errors=result.error+" (Code: "+result.errorcode+") Info: "+result.debuginfo;this.sync_string_errors=result.error,Y.log("Error: "+sync_errors,"debug","[Wifiresilience-SW] Sync")}this.save_failed(transactionid,response)}},save_failed:function(){this.real_connection_icon_status(!1),Y.log("Syncing with Exam Server: failed. Plan B: Local Storage.","debug","[Wifiresilience-SW] Sync"),this.save_transaction=null,this.update_status_for_failed_save(),this.save_start_time=null,this.dirty=!0,this.start_save_timer()},is_time_nearly_over:function(){calculated_delay=(new Date).getTime()+2*this.delay,time_nearly_over=M.mod_quiz.timer&&M.mod_quiz.timer.endtime&&calculated_delay>M.mod_quiz.timer.endtime},stop_autosaving:function(){this.cancel_delay(),this.delay_timer=!0,this.save_transaction&&this.save_transaction.abort()},warn_if_unsaved_data:function(e){var sessionstorage_attempt_key="wifiresilience-secondsleft-"+this.attemptid;if(navigator.onLine)sessionStorage.removeItem(sessionstorage_attempt_key);else{var secondsleft=Math.floor((M.mod_quiz.timer.endtime-(new Date).getTime())/1e3);sessionStorage.setItem(sessionstorage_attempt_key,secondsleft)}if(this.dirty||this.save_transaction){if(this.save_changes(),0==this.serviceworker_supported){var data={responses:Y.IO.stringify(M.quizaccess_wifiresilience.download.form)};M.quizaccess_wifiresilience.download.publicKey&&(data=M.quizaccess_wifiresilience.download.encryptResponses(data));var blob=new Blob([Y.JSON.stringify(data)],{type:"octet/stream"}),url=window.URL.createObjectURL(blob);$("#mod_quiz_navblock").append('')}return"undefined"!=typeof Storage&&(sessionStorage.setItem("wifiresilience-offline-"+this.attemptid,this.real_offline_time),sessionStorage.setItem("wifiresilience-offline-lastpage-"+this.attemptid,M.quizaccess_wifiresilience.navigation.currentpage)),e.returnValue=M.util.get_string("changesmadereallygoaway","quizaccess_wifiresilience"),e.returnValue}},submit_and_finish_clicked:function(e){e.halt(!0);var confirmationDialogue=new M.core.confirm({id:"submit-confirmation",width:"300px",center:!0,modal:!0,visible:!1,draggable:!1,title:M.util.get_string("confirmation","admin"),noLabel:M.util.get_string("cancel","moodle"),yesLabel:M.util.get_string("submitallandfinish","quiz"),question:M.util.get_string("confirmclose","quiz")});confirmationDialogue.on("complete-yes",this.submit_and_finish,this),confirmationDialogue.render().show()},submit_and_finish:function(e){e.preventDefault(),e.stopPropagation(),0==this.final_submission_time&&(this.final_submission_time=Math.round((new Date).getTime()/1e3)-this.real_offline_time),this.stop_autosaving();var submitButton=Y.one(this.SELECTORS.SUBMIT_BUTTON);this.get_submit_progress(submitButton.ancestor(".controls")).show(),submitButton.ancestor(".singlebutton").hide();var failureMessage=this.get_submit_failed_message(submitButton.ancestor(".controls"));submitButton.ancestor(".controls").removeClass("quiz-save-failed"),failureMessage.header.hide(),failureMessage.message.hide(),this.form.append(''),this.form.append(''),Y.log("Trying to do final submission to the server.. Brace! Brace! Brace! :-)","debug","[Wifiresilience-SW] Sync"),"undefined"!=typeof tinyMCE&&tinyMCE.triggerSave(),this.save_transaction=Y.io(this.AUTOSAVE_HANDLER,{method:"POST",form:{id:this.form},on:{success:this.submit_done,failure:this.submit_failed},context:this,timeout:this.SAVE_TIMEOUT_FULL_SUBMISSION,sync:!1}),this.save_start_time=new Date},submit_done:function(transactionid,response){var result;try{result=Y.JSON.parse(response.responseText)}catch(e){return Y.log("Final Submission Failure Reason (Transaction: "+transactionid+"): "+response.responseText,"debug","[Wifiresilience-SW] Sync"),void this.submit_failed(transactionid,response)}"OK"===result.result?(this.real_connection_icon_status(!0),Y.log("Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]","debug","[Wifiresilience-SW] Sync"),this.save_transaction=null,this.dirty=!1,Y.log("Deleting local records after successful exam..","debug","[Wifiresilience-SW] Sync"),M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(),window.location.replace(result.reviewurl)):this.submit_failed(transactionid,response)},submit_failed:function(transactionid=null,response=null){this.real_connection_icon_status(!1),Y.log("Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]","debug","[Wifiresilience-SW] Sync"),this.save_transaction=null,this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove();var submitButton=Y.one(this.SELECTORS.SUBMIT_BUTTON),submitProgress=this.get_submit_progress(submitButton.ancestor(".controls"));submitButton.ancestor(".singlebutton").show(),submitProgress.hide();var failureMessage=this.get_submit_failed_message(submitButton.ancestor(".controls")),submitAndFinishButton;function response_is_json_string(str){try{JSON.parse(str)}catch(e){return!1}return!0}submitButton.ancestor(".controls").addClass("quiz-save-failed"),failureMessage.header.show(),failureMessage.message.show(),this.update_status_for_failed_save(),Y.one(this.SELECTORS.SUBMIT_BUTTON).set("value",M.util.get_string("submitallandfinishtryagain","quizaccess_wifiresilience")),response&&(response_is_json_string(response.responseText)?(error_results=Y.JSON.parse(response.responseText),nice_merror_message=error_results.error+" ("+error_results.errorcode+")",Y.log(nice_merror_message,"debug","[Wifiresilience-SW] Sync")):(nice_merror_message="Server Connection Error.",Y.log(response,"debug","[Wifiresilience-SW] Sync"))),$("#wifi_debug_exam_error_details")&&$("#wifi_debug_exam_error_details").html("");var d,whenhappened=(new Date).toUTCString();1==this.display_tech_errors&&$(".submit-failed-message").append("