From 9ffc44b459f7c3ad7a80329bb1d7a2f96b838df2 Mon Sep 17 00:00:00 2001 From: Amr Date: Thu, 10 Mar 2022 22:45:48 +0100 Subject: [PATCH] Fixes EMDL-606 EMDL-605 EMDL-604 EMDL-578 --- attempt.php | 2 +- rule.php | 438 ++- serviceworker.php | 2 +- styles.css | 1 + time.php | 91 + upload.php | 430 +-- version.php | 4 +- ...uizaccess_wifiresilience-autosave-debug.js | 63 +- ...-quizaccess_wifiresilience-autosave-min.js | 1422 ++++++++- ...odle-quizaccess_wifiresilience-autosave.js | 2668 ++++++++-------- yui/src/autosave/js/autosave.js | 2750 +++++++++-------- 11 files changed, 4719 insertions(+), 3152 deletions(-) create mode 100755 time.php 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', '
'); + + $mform->addElement('checkbox', 'wifiresilience_prechecks', get_string('prechecks', 'quizaccess_wifiresilience')); + + $mform->addHelpButton('wifiresilience_prechecks', 'prechecks', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_prechecks', !empty($config->prechecks)); - - $mform->addElement('checkbox', 'wifiresilience_techerrors', - get_string('techerrors', 'quizaccess_wifiresilience')); - - $mform->addHelpButton('wifiresilience_techerrors', - 'techerrors', 'quizaccess_wifiresilience'); - + + $mform->addElement('checkbox', 'wifiresilience_techerrors', get_string('techerrors', 'quizaccess_wifiresilience')); + + $mform->addHelpButton('wifiresilience_techerrors', 'techerrors', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_techerrors', !empty($config->techerrors)); - - $mform->addElement('checkbox', 'wifiresilience_navdetails', - get_string('navdetails', 'quizaccess_wifiresilience')); - - $mform->addHelpButton('wifiresilience_navdetails', - 'navdetails', 'quizaccess_wifiresilience'); - + + $mform->addElement('checkbox', 'wifiresilience_navdetails', get_string('navdetails', 'quizaccess_wifiresilience')); + + $mform->addHelpButton('wifiresilience_navdetails', 'navdetails', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_navdetails', !empty($config->navdetails)); - - $mform->addElement('text', 'wifiresilience_wifitoken', - get_string('wifitoken', 'quizaccess_wifiresilience'), 'size="36"'); - - $mform->addHelpButton('wifiresilience_wifitoken', - 'wifitoken', 'quizaccess_wifiresilience'); - + + $mform->addElement('text', 'wifiresilience_wifitoken', get_string('wifitoken', 'quizaccess_wifiresilience'), 'size="36"'); + + $mform->addHelpButton('wifiresilience_wifitoken', 'wifitoken', 'quizaccess_wifiresilience'); + $mform->setType('wifiresilience_wifitoken', PARAM_RAW); $mform->setDefault('wifiresilience_wifitoken', $config->wifitoken); - - $mform->addElement('html', '
'); - - $mform->addElement('textarea', 'wifiresilience_watchxhr', - get_string('watchxhr', 'quizaccess_wifiresilience'), 'cols="60" rows="25"'); - - $mform->addHelpButton('wifiresilience_watchxhr', - 'watchxhr', 'quizaccess_wifiresilience'); - + + $mform->addElement('textarea', 'wifiresilience_watchxhr', get_string('watchxhr', 'quizaccess_wifiresilience'), + 'cols="60" rows="25"'); + + $mform->addHelpButton('wifiresilience_watchxhr', 'watchxhr', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_watchxhr', $config->watchxhr); - - $mform->addElement('textarea', 'wifiresilience_fetchandlog', - get_string('fetchandlog', 'quizaccess_wifiresilience'), 'cols="60" rows="5"'); - - $mform->addHelpButton('wifiresilience_fetchandlog', - 'fetchandlog', 'quizaccess_wifiresilience'); - + + $mform->addElement('textarea', 'wifiresilience_fetchandlog', get_string('fetchandlog', 'quizaccess_wifiresilience'), + 'cols="60" rows="5"'); + + $mform->addHelpButton('wifiresilience_fetchandlog', 'fetchandlog', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_fetchandlog', $config->fetchandlog); - - $mform->addElement('textarea', 'wifiresilience_precachefiles', - get_string('precachefiles', 'quizaccess_wifiresilience'), 'cols="60" rows="5"'); - - $mform->addHelpButton('wifiresilience_precachefiles', - 'precachefiles', 'quizaccess_wifiresilience'); - + + $mform->addElement('textarea', 'wifiresilience_precachefiles', get_string('precachefiles', 'quizaccess_wifiresilience'), + 'cols="60" rows="5"'); + + $mform->addHelpButton('wifiresilience_precachefiles', 'precachefiles', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_precachefiles', $config->precachefiles); - - $mform->addElement('textarea', 'wifiresilience_excludelist', - get_string('excludelist', 'quizaccess_wifiresilience'), 'cols="60" rows="5"'); - - $mform->addHelpButton('wifiresilience_excludelist', - 'excludelist', 'quizaccess_wifiresilience'); - + + $mform->addElement('textarea', 'wifiresilience_excludelist', get_string('excludelist', 'quizaccess_wifiresilience'), + 'cols="60" rows="5"'); + + $mform->addHelpButton('wifiresilience_excludelist', 'excludelist', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_excludelist', $config->excludelist); - - $mform->addElement('textarea', 'wifiresilience_extraroutes', - get_string('extraroutes', 'quizaccess_wifiresilience'), 'cols="60" rows="25"'); - - $mform->addHelpButton('wifiresilience_extraroutes', - 'extraroutes', 'quizaccess_wifiresilience'); - + + $mform->addElement('textarea', 'wifiresilience_extraroutes', get_string('extraroutes', 'quizaccess_wifiresilience'), + 'cols="60" rows="25"'); + + $mform->addHelpButton('wifiresilience_extraroutes', 'extraroutes', 'quizaccess_wifiresilience'); + $mform->setDefault('wifiresilience_extraroutes', $config->extraroutes); - + $mform->disabledIf('wifiresilience_prechecks', 'wifiresilience_enabled', 'eq', 0); $mform->disabledIf('wifiresilience_techerrors', 'wifiresilience_enabled', 'eq', 0); $mform->disabledIf('wifiresilience_navdetails', 'wifiresilience_enabled', 'eq', 0); @@ -175,7 +153,7 @@ public static function add_settings_form_fields(mod_quiz_mod_form $quizform, Moo $mform->disabledIf('wifiresilience_extraroutes', 'wifiresilience_enabled', 'eq', 0); $mform->disabledIf('wifiresilience_precachefiles', 'wifiresilience_enabled', 'eq', 0); $mform->disabledIf('wifiresilience_excludelist', 'wifiresilience_enabled', 'eq', 0); - + foreach (question_engine::get_behaviour_options(null) as $behaviour => $notused) { if (!self::is_compatible_behaviour($behaviour)) { $mform->disabledIf('wifiresilience_enabled', 'preferredbehaviour', 'eq', $behaviour); @@ -196,7 +174,9 @@ public static function add_settings_form_fields(mod_quiz_mod_form $quizform, Moo /** * Given the quiz "How questions behave" setting, can the fault-tolerant mode work * with that behaviour? - * @param string $behaviour the internal name (e.g. 'interactive') of an archetypal behaviour. + * + * @param string $behaviour + * the internal name (e.g. 'interactive') of an archetypal behaviour. * @return boolean whether fault-tolerant mode can be used. */ public static function is_compatible_behaviour($behaviour) { @@ -208,12 +188,14 @@ public static function is_compatible_behaviour($behaviour) { /** * Save settings - * @param object $quiz the data from the quiz form, including $quiz->id - * which is the id of the quiz being saved + * + * @param object $quiz + * the data from the quiz form, including $quiz->id + * which is the id of the quiz being saved */ public static function save_settings($quiz) { global $DB; - + if (empty($quiz->wifiresilience_enabled)) { $DB->delete_records('quizaccess_wifiresilience', array('quizid' => $quiz->id)); } else { @@ -247,7 +229,7 @@ public static function save_settings($quiz) { if ($DB->record_exists('quizaccess_wifiresilience', array('quizid' => $quiz->id))) { $DB->delete_records('quizaccess_wifiresilience', array('quizid' => $quiz->id)); } - + $record = new stdClass(); $record->quizid = $quiz->id; $record->enabled = 1; @@ -260,15 +242,17 @@ public static function save_settings($quiz) { $record->extraroutes = $quiz->wifiresilience_extraroutes; $record->precachefiles = $quiz->wifiresilience_precachefiles; $record->excludelist = $quiz->wifiresilience_excludelist; - + $DB->insert_record('quizaccess_wifiresilience', $record); } } /** * Delete settings - * @param object $quiz the data from the database, including $quiz->id - * which is the id of the quiz being deleted. + * + * @param object $quiz + * the data from the database, including $quiz->id + * which is the id of the quiz being deleted. */ public static function delete_settings($quiz) { global $DB; @@ -277,13 +261,13 @@ public static function delete_settings($quiz) { /** * Get settings - * @param int $quizid the id of the quiz we are loading settings for. + * + * @param int $quizid + * the id of the quiz we are loading settings for. */ public static function get_settings_sql($quizid) { - return array( - 'COALESCE(wifiresilience.enabled, 0) AS wifiresilience_enabled', - 'LEFT JOIN {quizaccess_wifiresilience} wifiresilience ON wifiresilience.quizid = quiz.id', - array()); + return array('COALESCE(wifiresilience.enabled, 0) AS wifiresilience_enabled', + 'LEFT JOIN {quizaccess_wifiresilience} wifiresilience ON wifiresilience.quizid = quiz.id', array()); } /** @@ -291,189 +275,161 @@ public static function get_settings_sql($quizid) { */ public function description() { global $CFG, $DB, $USER, $PAGE; - + $displayadminmsgs = 0; - + if ($this->quizobj->has_capability('quizaccess/wifiresilience:adminmessages')) { $displayadminmsgs = 1; } - + $uploadresponsesrole = 0; if ($this->quizobj->has_capability('quizaccess/wifiresilience:uploadresponses')) { $uploadresponsesrole = 1; } - + $inspectresponsesrole = 0; if ($this->quizobj->has_capability('quizaccess/wifiresilience:inspectresponses')) { $inspectresponsesrole = 1; } - + $localresponsesrole = 0; if ($this->quizobj->has_capability('quizaccess/wifiresilience:localresponses')) { $localresponsesrole = 1; } - + $browserchecksrole = 0; if ($this->quizobj->has_capability('quizaccess/wifiresilience:browserchecks')) { $browserchecksrole = 1; } - + $viewtechchecksrole = 0; if ($this->quizobj->has_capability('quizaccess/wifiresilience:viewtechchecks')) { $viewtechchecksrole = 1; } - + $quizid = $this->quizobj->get_quizid(); if (!$quizid) { print_error('invalidcourse'); } - + $quizcmid = $this->quizobj->get_cmid(); if (!$quizcmid) { print_error('invalidcoursemodule'); } - + $serviceworkerparams = '?cmid=' . $quizcmid . '&quizid=' . $quizid . '&rev=' . rand(); - - $showtechprechecks = (!empty($wifisettings->prechecks) && $wifisettings->prechecks != 0) - || $viewtechchecksrole == 1 || $displayadminmsgs == 1; - + + $showtechprechecks = (!empty($wifisettings->prechecks) && $wifisettings->prechecks != 0) || $viewtechchecksrole == 1 || + $displayadminmsgs == 1; + $PAGE->requires->jquery(); $PAGE->requires->js('/mod/quiz/accessrule/wifiresilience/js/localforage.js', true); $PAGE->requires->js('/mod/quiz/accessrule/wifiresilience/js/startswith.js', true); - + $PAGE->requires->strings_for_js( - array( - 'rule1start', 'rule1success', 'rule1fail', 'rule1error', - 'rule1statusactive', 'rule1statusinstalling', 'rule1statuswaiting', - 'rule2start', 'rule2success', 'rule2error', - 'rule3start', 'rule3success', 'rule3error', - 'rule4start', 'rule4success', 'rule4fail', 'rule4error', - 'rule5start', 'rule5success', 'rule5fail', 'rule5error', - 'rule6start', 'rule6success', 'rule6error', - 'rule7start', 'rule7success', 'rule7error', - 'rulebgsyncsuccess', 'rulebgsyncfail', 'rulebgsyncsupported', - 'ruleswnotregisteredreset', 'ruleswnotregisteredstop', 'ruleswnotregisteredupdate' - ), - 'quizaccess_wifiresilience'); - - $PAGE->requires->yui_module( - 'moodle-quizaccess_wifiresilience-initialiserule', - 'M.quizaccess_wifiresilience.initialiserule.init', - array( - $serviceworkerparams, - $displayadminmsgs, - $showtechprechecks - ) - ); - - $wifisettings = $DB->get_record('quizaccess_wifiresilience', array('quizid' => $quizid)); - - $return = ''; - + array('rule1start', 'rule1success', 'rule1fail', 'rule1error', 'rule1statusactive', + 'rule1statusinstalling', 'rule1statuswaiting', 'rule2start', 'rule2success', + 'rule2error', 'rule3start', 'rule3success', 'rule3error', 'rule4start', 'rule4success', + 'rule4fail', 'rule4error', 'rule5start', 'rule5success', 'rule5fail', 'rule5error', + 'rule6start', 'rule6success', 'rule6error', 'rule7start', 'rule7success', 'rule7error', + 'rulebgsyncsuccess', 'rulebgsyncfail', 'rulebgsyncsupported', 'ruleswnotregisteredreset', + 'ruleswnotregisteredstop', 'ruleswnotregisteredupdate'), 'quizaccess_wifiresilience'); + + $PAGE->requires->yui_module('moodle-quizaccess_wifiresilience-initialiserule', + 'M.quizaccess_wifiresilience.initialiserule.init', + array($serviceworkerparams, $displayadminmsgs, $showtechprechecks)); + + $wifisettings = get_config('quizaccess_wifiresilience'); + + $return = ''; + if ($displayadminmsgs == 1) { // If mobile services are off, the user won't be able to use any external app. if (empty($CFG->enablemobilewebservice) || empty($wifisettings->wifitoken) || trim($wifisettings->wifitoken) == '') { - + $return .= html_writer::start_div('alert alert-error', array('style' => "text-align: left;")) . - get_string('webservicedisabled', 'quizaccess_wifiresilience') . - html_writer::start_tag('ol'); - + get_string('webservicedisabled', 'quizaccess_wifiresilience') . html_writer::start_tag('ol'); + if (empty($CFG->enablemobilewebservice)) { - $return .= html_writer::tag('li', - get_string('webserviceenablemobile', 'quizaccess_wifiresilience', - $CFG->wwwroot)); + $return .= html_writer::tag('li', + get_string('webserviceenablemobile', 'quizaccess_wifiresilience', $CFG->wwwroot)); } if (empty($wifisettings->wifitoken) || trim($wifisettings->wifitoken) == '') { - $return .= html_writer::tag('li', - get_string('webserviceaddtoken', 'quizaccess_wifiresilience', - array("wwwroot" => $CFG->wwwroot, "quizcmid" => $quizcmid))); + $return .= html_writer::tag('li', + get_string('webserviceaddtoken', 'quizaccess_wifiresilience', + array("wwwroot" => $CFG->wwwroot, "quizcmid" => $quizcmid))); } - $return .= html_writer::end_tag('ol') . - html_writer::end_div(''); + $return .= html_writer::end_tag('ol') . html_writer::end_div(''); } } - - if ($displayadminmsgs == 1 - || $uploadresponsesrole == 1 - || $inspectresponsesrole == 1 - || $browserchecksrole == 1 - || $localresponsesrole == 1) { - $return .= '
' . - get_string('description', 'quizaccess_wifiresilience'). - '
' . - '
' . - get_string('uploadresponsesadmin', 'quizaccess_wifiresilience'); - $return .= '
    '; + + if ($displayadminmsgs == 1 || $uploadresponsesrole == 1 || $inspectresponsesrole == 1 || $browserchecksrole == 1 || + $localresponsesrole == 1) { + $return .= '
    ' . get_string('description', 'quizaccess_wifiresilience') . '
    ' . + '
    ' . + get_string('uploadresponsesadmin', 'quizaccess_wifiresilience'); + $return .= '
      '; } - + if ($uploadresponsesrole == 1) { - $return .= '
    • ' . html_writer::link(new moodle_url('/mod/quiz/accessrule/wifiresilience/upload.php', - array('id' => $quizcmid)), - get_string('descriptionlink', 'quizaccess_wifiresilience')) . '
    • '; + $return .= '
    • ' . html_writer::link( + new moodle_url('/mod/quiz/accessrule/wifiresilience/upload.php', + array('id' => $quizcmid)), + get_string('descriptionlink', 'quizaccess_wifiresilience')) . '
    • '; } - + if ($localresponsesrole == 1) { - $return .= '
    • ' . html_writer::link(new moodle_url('/mod/quiz/accessrule/wifiresilience/local.php', - array('id' => $quizcmid)), - get_string('loadlocalresponses', 'quizaccess_wifiresilience')) . '
    • '; - - $return .= '
    • ' . html_writer::link(new moodle_url('/mod/quiz/accessrule/wifiresilience/uploaded_syncedfiles.php', - array('id' => $quizcmid)), - get_string('syncedfiles', 'quizaccess_wifiresilience')) . '
    • '; + $return .= '
    • ' . + html_writer::link( + new moodle_url('/mod/quiz/accessrule/wifiresilience/local.php', array('id' => $quizcmid)), + get_string('loadlocalresponses', 'quizaccess_wifiresilience')) . '
    • '; + + $return .= '
    • ' . + html_writer::link( + new moodle_url('/mod/quiz/accessrule/wifiresilience/uploaded_syncedfiles.php', array('id' => $quizcmid)), + get_string('syncedfiles', 'quizaccess_wifiresilience')) . '
    • '; } - + if ($inspectresponsesrole == 1) { - $return .= '
    • ' . html_writer::link(new moodle_url('/mod/quiz/accessrule/wifiresilience/inspect.php', - array('id' => $quizcmid)), - get_string('inspect', 'quizaccess_wifiresilience')) . '
    • '; + $return .= '
    • ' . html_writer::link( + new moodle_url('/mod/quiz/accessrule/wifiresilience/inspect.php', + array('id' => $quizcmid)), + get_string('inspect', 'quizaccess_wifiresilience')) . '
    • '; } - - if ($displayadminmsgs == 1 - || $uploadresponsesrole == 1 - || $inspectresponsesrole == 1 - || $browserchecksrole == 1 - || $localresponsesrole == 1) { + + if ($displayadminmsgs == 1 || $uploadresponsesrole == 1 || $inspectresponsesrole == 1 || $browserchecksrole == 1 || + $localresponsesrole == 1) { $return .= '
    '; } - + if ($displayadminmsgs == 1) { $return .= '

    ' . get_string('serviceworkermgmt', 'quizaccess_wifiresilience') . - '

    - ' . - '
    '; + ' + ' . + '
    '; } - - if ($displayadminmsgs == 1 - || $uploadresponsesrole == 1 - || $inspectresponsesrole == 1 - || $browserchecksrole == 1 - || $localresponsesrole == 1) { + + if ($displayadminmsgs == 1 || $uploadresponsesrole == 1 || $inspectresponsesrole == 1 || $browserchecksrole == 1 || + $localresponsesrole == 1) { $return .= '
    '; } - - $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("

    "+whenhappened+": "+nice_merror_message+"
    ");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);$(".submit-failed-message").append('')},get_submit_progress:function(controlsDiv){var submitProgress=controlsDiv.one(".submit-progress");return submitProgress||(submitProgress=controlsDiv.appendChild('
    '),M.util.add_spinner(Y,submitProgress).show(),submitProgress.append(M.util.get_string("submitting","quizaccess_wifiresilience")),submitProgress)},get_submit_failed_message:function(controlsDiv){var failedHeader=controlsDiv.one(".submit-failed-header");if(failedHeader)return{header:failedHeader,message:controlsDiv.one(".submit-failed-message")};controlsDiv.insert('
    ',0),(failedHeader=controlsDiv.one(".submit-failed-header")).append("

    "+M.util.get_string("submitfailed","quizaccess_wifiresilience")+"

    "),failedHeader.append("

    "+M.util.get_string("submitfailedmessage","quizaccess_wifiresilience")+"

    ");var downloadLink=''+M.util.get_string("savetheresponses","quizaccess_wifiresilience")+"",failedMessage=controlsDiv.appendChild('
    ');return failedMessage.append("

    "+M.util.get_string("submitfaileddownloadmessage","quizaccess_wifiresilience",downloadLink)+"

    "),{header:failedHeader,message:failedMessage}},create_status_messages:function(){var last_saved_msg_str="",saving_dots_str="";1==this.display_nav_details&&(last_saved_msg_str=M.util.get_string("lastsaved","quizaccess_wifiresilience",''),saving_dots_str=M.util.get_string("savingdots","quizaccess_wifiresilience")),Y.one("#mod_quiz_navblock .content").append('
    '+last_saved_msg_str+'
    '+saving_dots_str+'
    '),this.save_start_time=new Date,this.update_status_for_successful_save(),this.save_start_time=null},exam_saving_time_pad:function(number){return number<10?"0"+number:number},exam_localstorage_saving_status_str:function(){var latest_localstorage_timing=new Date(this.locally_stored_data.last_change);1==this.display_nav_details&&Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours())+":"+this.exam_saving_time_pad(this.last_successful_save.getMinutes())+":"+this.exam_saving_time_pad(this.last_successful_save.getSeconds())+M.util.get_string("localstorage","quizaccess_wifiresilience")+this.exam_saving_time_pad(latest_localstorage_timing.getHours())+":"+this.exam_saving_time_pad(latest_localstorage_timing.getMinutes())+":"+this.exam_saving_time_pad(latest_localstorage_timing.getSeconds()))},update_status_for_successful_save:function(){function pad(number){return number<10?"0"+number:number}wifi_current_time=Date(),this.last_successful_save=new Date(wifi_current_time),this.last_successful_server_save_timestamp=new Date(wifi_current_time),this.exam_localstorage_saving_status_str(),Y.one(this.SELECTORS.SAVING_NOTICE).setStyle("display","none"),Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string("savingdots","quizaccess_wifiresilience")),Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).hide(),this.locally_stored_data.last_save=this.last_successful_server_save_timestamp;var stringified_data=Y.JSON.stringify(this.locally_stored_data);M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data),this.save_start_time=null},update_status_for_failed_save:function(){1==this.display_nav_details&&(Y.one(this.SELECTORS.LAST_SAVED_MESSAGE).setHTML(M.util.get_string("lastsavedtotheserver","quizaccess_wifiresilience",Y.one(this.SELECTORS.LAST_SAVED_TIME).get("outerHTML"))),Y.one(this.SELECTORS.SAVING_NOTICE).setStyle("display","none"),Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string("savingtryagaindots","quizaccess_wifiresilience")+"
    "),Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).show())},try_to_restore_session:function(){this.loginDialogue=new M.core.notification.info({id:"quiz-relogin-dialogue",width:"70%",center:!0,modal:!0,visible:!1,draggable:!1}),this.loginDialogue.setStdModContent(Y.WidgetStdMod.HEADER,'

    '+M.util.get_string("logindialogueheader","quizaccess_wifiresilience")+"

    ",Y.WidgetStdMod.REPLACE),this.loginDialogue.setStdModContent(Y.WidgetStdMod.BODY,''); + } + + // Try to set real offline time from SessionStorage for people who are refreshing the attempt. + if (typeof(Storage) !== "undefined") { + sessionStorage.setItem('wifiresilience-offline-' + this.attemptid, this.real_offline_time); + // Now insert in session storage for refresh in service worker :). + sessionStorage.setItem('wifiresilience-offline-lastpage-' + this.attemptid, M.quizaccess_wifiresilience.navigation.currentpage); + } + // Now Show a warning. + e.returnValue = M.util.get_string('changesmadereallygoaway', 'quizaccess_wifiresilience'); + return e.returnValue; + }, + + /** + * Handle a click on the submit and finish button. That is, show a confirm dialogue. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish_clicked: function(e) { + e.halt(true); + + var confirmationDialogue = new M.core.confirm({ + id: 'submit-confirmation', + width: '300px', + center: true, + modal: true, + visible: false, + draggable: false, + 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') + }); + + // The dialogue was submitted with a positive value indication. + confirmationDialogue.on('complete-yes', this.submit_and_finish, this); + confirmationDialogue.render().show(); + }, + + /** + * Handle the submit and finish button in the confirm dialogue being pressed. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish: function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Preserve time for submission + // Todo: substract total offline time (qtype fileresponse, complie code etc). + if (this.final_submission_time == 0) { + this.final_submission_time = Math.round(new Date().getTime() / 1000) - 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'); + + if (typeof tinyMCE !== 'undefined') { + 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: false + }); + this.save_start_time = new Date(); + }, + + submit_done: function(transactionid, response) { + var result; + try { + result = Y.JSON.parse(response.responseText); + } catch (e) { + Y.log('Final Submission Failure Reason (Transaction: ' + transactionid + '): ' + response.responseText, 'debug', + '[Wifiresilience-SW] Sync'); + this.submit_failed(transactionid, response); + return; + } + + if (result.result !== 'OK') { + this.submit_failed(transactionid, response); + return; + } + this.real_connection_icon_status(true); + Y.log('Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + this.dirty = false; + // Try to delete the local data records via promises :). + + Y.log('Deleting local records after successful exam..', 'debug', + '[Wifiresilience-SW] Sync'); + M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(); + + // Go to Exam Review URL. + window.location.replace(result.reviewurl); + }, + + submit_failed: function(transactionid = null, response = null) { + this.real_connection_icon_status(false); + Y.log('Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + + // Re-display the submit button. + this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove(); + var submitButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + var submitProgress = this.get_submit_progress(submitButton.ancestor('.controls')); + submitButton.ancestor('.singlebutton').show(); + submitProgress.hide(); + + // And show the failure message. + var failureMessage = this.get_submit_failed_message(submitButton.ancestor('.controls')); + submitButton.ancestor('.controls').addClass('quiz-save-failed'); + failureMessage.header.show(); + failureMessage.message.show(); + + this.update_status_for_failed_save(); + + // Change the label of submit again to "try again". + var submitAndFinishButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + submitAndFinishButton.set('value',M.util.get_string('submitallandfinishtryagain', 'quizaccess_wifiresilience')); + + function response_is_json_string(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } + + if (response) { + if (response_is_json_string(response.responseText)) { // Moodle validation issues. + 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'); + } else { // Server issues. + nice_merror_message = "Server Connection Error."; + Y.log(response, 'debug', '[Wifiresilience-SW] Sync'); + } + } + if ($("#wifi_debug_exam_error_details")) { + $("#wifi_debug_exam_error_details").html(''); + } + var d = new Date(); + var whenhappened = d.toUTCString(); + + if (this.display_tech_errors == 1) { + $(".submit-failed-message").append("

    " + + whenhappened + + ": " + nice_merror_message + "
    "); + } + // Now do that sourceforge style automatic download. + var data = {responses: Y.IO.stringify(M.quizaccess_wifiresilience.download.form)}; + if (M.quizaccess_wifiresilience.download.publicKey) { + data = M.quizaccess_wifiresilience.download.encryptResponses(data); + } + var blob = new Blob([Y.JSON.stringify(data)], {type: "octet/stream"}); + var url = window.URL.createObjectURL(blob); + $(".submit-failed-message").append( + ''); + }, + + get_submit_progress: function(controlsDiv) { + var submitProgress = controlsDiv.one('.submit-progress'); + if (submitProgress) { + // Already created. Return it. + return submitProgress; + } + + // Needs to be created. + submitProgress = controlsDiv.appendChild('
    '); + M.util.add_spinner(Y, submitProgress).show(); + submitProgress.append(M.util.get_string('submitting', 'quizaccess_wifiresilience')); + return submitProgress; + }, + + get_submit_failed_message: function(controlsDiv) { + var failedHeader = controlsDiv.one('.submit-failed-header'); + if (failedHeader) { + // Already created. Return it. + return {header: failedHeader, message: controlsDiv.one('.submit-failed-message')}; + } + + // Needs to be created. + controlsDiv.insert('
    ', 0); + failedHeader = controlsDiv.one('.submit-failed-header'); + failedHeader.append('

    ' + M.util.get_string('submitfailed', 'quizaccess_wifiresilience') + '

    '); + failedHeader.append('

    ' + M.util.get_string('submitfailedmessage', 'quizaccess_wifiresilience') + '

    '); + + var downloadLink = '' + + M.util.get_string('savetheresponses', 'quizaccess_wifiresilience') + ''; + var failedMessage = controlsDiv.appendChild('
    '); + failedMessage.append( + '

    ' + M.util.get_string('submitfaileddownloadmessage', 'quizaccess_wifiresilience', downloadLink) + '

    '); + + return {header: failedHeader, message: failedMessage}; + }, + + create_status_messages: function() { + + var last_saved_msg_str = ''; + var saving_dots_str = ''; + + if (this.display_nav_details == 1) { + last_saved_msg_str = M.util.get_string('lastsaved', 'quizaccess_wifiresilience', ''); + saving_dots_str = M.util.get_string('savingdots', 'quizaccess_wifiresilience'); + } + + Y.one('#mod_quiz_navblock .content').append('
    ' + + '
    ' + last_saved_msg_str + '
    ' + + '
    ' + saving_dots_str + '
    ' + + '
    ' + + '
    '); + + this.save_start_time = new Date(); + this.update_status_for_successful_save(); + this.save_start_time = null; + }, + + exam_saving_time_pad: function(number) { + return number < 10 ? '0' + number : number; + }, + exam_localstorage_saving_status_str: function() { + + var latest_localstorage_timing = new Date(this.locally_stored_data.last_change); + if (this.display_nav_details == 1) { + + Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getMinutes()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getSeconds()) + + M.util.get_string('localstorage', 'quizaccess_wifiresilience') + + this.exam_saving_time_pad(latest_localstorage_timing.getHours()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getMinutes()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getSeconds())); + } + }, + + update_status_for_successful_save: function() { + + function pad(number) { + return number < 10 ? '0' + number : number; + } + wifi_current_time = Date(); + this.last_successful_save = new Date(wifi_current_time); + this.last_successful_server_save_timestamp = new Date(wifi_current_time); + this.exam_localstorage_saving_status_str(); + + Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); + Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string('savingdots', 'quizaccess_wifiresilience')); + Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).hide(); + + this.locally_stored_data.last_save = this.last_successful_server_save_timestamp; + + var stringified_data = Y.JSON.stringify(this.locally_stored_data); + // IndexedDB or WebSQL. + M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data); + + this.save_start_time = null; + }, + + update_status_for_failed_save: function() { + if (this.display_nav_details == 1) { + Y.one(this.SELECTORS.LAST_SAVED_MESSAGE).setHTML( + M.util.get_string('lastsavedtotheserver', 'quizaccess_wifiresilience', + Y.one(this.SELECTORS.LAST_SAVED_TIME).get('outerHTML'))); + Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); + + Y.one(this.SELECTORS.SAVING_NOTICE).setHTML( + M.util.get_string('savingtryagaindots', 'quizaccess_wifiresilience') + '
    '); + Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).show(); + } + }, + + try_to_restore_session: function() { + this.loginDialogue = new M.core.notification.info({ + id: 'quiz-relogin-dialogue', + width: '70%', + center: true, + modal: true, + visible: false, + draggable: false + }); + + this.loginDialogue.setStdModContent(Y.WidgetStdMod.HEADER, + '

    ' + + M.util.get_string('logindialogueheader', 'quizaccess_wifiresilience') + + '

    ', Y.WidgetStdMod.REPLACE); + this.loginDialogue.setStdModContent(Y.WidgetStdMod.BODY, + ''); + } + + // Try to set real offline time from SessionStorage for people who are refreshing the attempt. + if (typeof(Storage) !== "undefined") { + sessionStorage.setItem('wifiresilience-offline-' + this.attemptid, this.real_offline_time); + // Now insert in session storage for refresh in service worker :). + sessionStorage.setItem('wifiresilience-offline-lastpage-' + this.attemptid, M.quizaccess_wifiresilience.navigation.currentpage); + } + // Now Show a warning. + e.returnValue = M.util.get_string('changesmadereallygoaway', 'quizaccess_wifiresilience'); + return e.returnValue; + }, + + /** + * Handle a click on the submit and finish button. That is, show a confirm dialogue. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish_clicked: function(e) { + e.halt(true); + + var confirmationDialogue = new M.core.confirm({ + id: 'submit-confirmation', + width: '300px', + center: true, + modal: true, + visible: false, + draggable: false, + 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') + }); + + // The dialogue was submitted with a positive value indication. + confirmationDialogue.on('complete-yes', this.submit_and_finish, this); + confirmationDialogue.render().show(); + }, + + /** + * Handle the submit and finish button in the confirm dialogue being pressed. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish: function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Preserve time for submission + // Todo: substract total offline time (qtype fileresponse, complie code etc). + if (this.final_submission_time == 0) { + this.final_submission_time = Math.round(new Date().getTime() / 1000) - 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'); + + if (typeof tinyMCE !== 'undefined') { + 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: false + }); + this.save_start_time = new Date(); + }, + + submit_done: function(transactionid, response) { + var result; + try { + result = Y.JSON.parse(response.responseText); + } catch (e) { + Y.log('Final Submission Failure Reason (Transaction: ' + transactionid + '): ' + response.responseText, 'debug', + '[Wifiresilience-SW] Sync'); + this.submit_failed(transactionid, response); + return; + } + + if (result.result !== 'OK') { + this.submit_failed(transactionid, response); + return; + } + this.real_connection_icon_status(true); + Y.log('Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + this.dirty = false; + // Try to delete the local data records via promises :). + + Y.log('Deleting local records after successful exam..', 'debug', + '[Wifiresilience-SW] Sync'); + M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(); + + // Go to Exam Review URL. + window.location.replace(result.reviewurl); + }, + + submit_failed: function(transactionid = null, response = null) { + this.real_connection_icon_status(false); + Y.log('Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + + // Re-display the submit button. + this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove(); + var submitButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + var submitProgress = this.get_submit_progress(submitButton.ancestor('.controls')); + submitButton.ancestor('.singlebutton').show(); + submitProgress.hide(); + + // And show the failure message. + var failureMessage = this.get_submit_failed_message(submitButton.ancestor('.controls')); + submitButton.ancestor('.controls').addClass('quiz-save-failed'); + failureMessage.header.show(); + failureMessage.message.show(); + + this.update_status_for_failed_save(); + + // Change the label of submit again to "try again". + var submitAndFinishButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + submitAndFinishButton.set('value',M.util.get_string('submitallandfinishtryagain', 'quizaccess_wifiresilience')); + + function response_is_json_string(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } + + if (response) { + if (response_is_json_string(response.responseText)) { // Moodle validation issues. + 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'); + } else { // Server issues. + nice_merror_message = "Server Connection Error."; + Y.log(response, 'debug', '[Wifiresilience-SW] Sync'); + } + } + if ($("#wifi_debug_exam_error_details")) { + $("#wifi_debug_exam_error_details").html(''); + } + var d = new Date(); + var whenhappened = d.toUTCString(); + + if (this.display_tech_errors == 1) { + $(".submit-failed-message").append("

    " + + whenhappened + + ": " + nice_merror_message + "
    "); + } + // Now do that sourceforge style automatic download. var data = {responses: Y.IO.stringify(M.quizaccess_wifiresilience.download.form)}; - if (M.quizaccess_wifiresilience.download.publicKey) { data = M.quizaccess_wifiresilience.download.encryptResponses(data); } - var blob = new Blob([Y.JSON.stringify(data)], {type: "octet/stream"}); var url = window.URL.createObjectURL(blob); - $("#mod_quiz_navblock").append( - ''); - } - - // Try to set real offline time from SessionStorage for people who are refreshing the attempt. - if (typeof(Storage) !== "undefined") { - sessionStorage.setItem('wifiresilience-offline-' + this.attemptid, this.real_offline_time); - // Now insert in session storage for refresh in service worker :). - sessionStorage.setItem('wifiresilience-offline-lastpage-' + this.attemptid, M.quizaccess_wifiresilience.navigation.currentpage); - } - // Now Show a warning. - e.returnValue = M.util.get_string('changesmadereallygoaway', 'quizaccess_wifiresilience'); - return e.returnValue; - }, - - /** - * Handle a click on the submit and finish button. That is, show a confirm dialogue. - * - * @param {EventFacade} e The triggering event, if there is one. - */ - submit_and_finish_clicked: function(e) { - e.halt(true); - - var confirmationDialogue = new M.core.confirm({ - id: 'submit-confirmation', - width: '300px', - center: true, - modal: true, - visible: false, - draggable: false, - 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') - }); - - // The dialogue was submitted with a positive value indication. - confirmationDialogue.on('complete-yes', this.submit_and_finish, this); - confirmationDialogue.render().show(); - }, - - /** - * Handle the submit and finish button in the confirm dialogue being pressed. - * - * @param {EventFacade} e The triggering event, if there is one. - */ - submit_and_finish: function(e) { - e.preventDefault(); - e.stopPropagation(); - - // Preserve time for submission - // Todo: substract total offline time (qtype fileresponse, complie code etc). - if (this.final_submission_time == 0) { - this.final_submission_time = Math.round(new Date().getTime() / 1000) - 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'); - - if (typeof tinyMCE !== 'undefined') { - 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: false - }); - this.save_start_time = new Date(); - }, - - submit_done: function(transactionid, response) { - var result; - try { - result = Y.JSON.parse(response.responseText); - } catch (e) { - Y.log('Final Submission Failure Reason (Transaction: ' + transactionid + '): ' + response.responseText, 'debug', - '[Wifiresilience-SW] Sync'); - this.submit_failed(transactionid, response); - return; - } - - if (result.result !== 'OK') { - this.submit_failed(transactionid, response); - return; - } - this.real_connection_icon_status(true); - Y.log('Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]', 'debug', - '[Wifiresilience-SW] Sync'); - this.save_transaction = null; - this.dirty = false; - // Try to delete the local data records via promises :). - - Y.log('Deleting local records after successful exam..', 'debug', - '[Wifiresilience-SW] Sync'); - M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(); - - // Go to Exam Review URL. - window.location.replace(result.reviewurl); - }, - - submit_failed: function(transactionid = null, response = null) { - this.real_connection_icon_status(false); - Y.log('Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]', 'debug', - '[Wifiresilience-SW] Sync'); - this.save_transaction = null; - - // Re-display the submit button. - this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove(); - var submitButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); - var submitProgress = this.get_submit_progress(submitButton.ancestor('.controls')); - submitButton.ancestor('.singlebutton').show(); - submitProgress.hide(); - - // And show the failure message. - var failureMessage = this.get_submit_failed_message(submitButton.ancestor('.controls')); - submitButton.ancestor('.controls').addClass('quiz-save-failed'); - failureMessage.header.show(); - failureMessage.message.show(); - - this.update_status_for_failed_save(); - - // Change the label of submit again to "try again". - var submitAndFinishButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); - submitAndFinishButton.set('value',M.util.get_string('submitallandfinishtryagain', 'quizaccess_wifiresilience')); - - function response_is_json_string(str) { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - } - - if (response) { - if (response_is_json_string(response.responseText)) { // Moodle validation issues. - 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'); - } else { // Server issues. - nice_merror_message = "Server Connection Error."; - Y.log(response, 'debug', '[Wifiresilience-SW] Sync'); + $(".submit-failed-message").append( + ''); + }, + + get_submit_progress: function(controlsDiv) { + var submitProgress = controlsDiv.one('.submit-progress'); + if (submitProgress) { + // Already created. Return it. + return submitProgress; } - } - if ($("#wifi_debug_exam_error_details")) { - $("#wifi_debug_exam_error_details").html(''); - } - var d = new Date(); - var whenhappened = d.toUTCString(); - - if (this.display_tech_errors == 1) { - $(".submit-failed-message").append("

    " + - whenhappened + - ": " + nice_merror_message + "
    "); - } - // Now do that sourceforge style automatic download. - var data = {responses: Y.IO.stringify(M.quizaccess_wifiresilience.download.form)}; - if (M.quizaccess_wifiresilience.download.publicKey) { - data = M.quizaccess_wifiresilience.download.encryptResponses(data); - } - var blob = new Blob([Y.JSON.stringify(data)], {type: "octet/stream"}); - var url = window.URL.createObjectURL(blob); - $(".submit-failed-message").append( - ''); - }, - - get_submit_progress: function(controlsDiv) { - var submitProgress = controlsDiv.one('.submit-progress'); - if (submitProgress) { - // Already created. Return it. + + // Needs to be created. + submitProgress = controlsDiv.appendChild('
    '); + M.util.add_spinner(Y, submitProgress).show(); + submitProgress.append(M.util.get_string('submitting', 'quizaccess_wifiresilience')); return submitProgress; - } - - // Needs to be created. - submitProgress = controlsDiv.appendChild('
    '); - M.util.add_spinner(Y, submitProgress).show(); - submitProgress.append(M.util.get_string('submitting', 'quizaccess_wifiresilience')); - return submitProgress; - }, - - get_submit_failed_message: function(controlsDiv) { - var failedHeader = controlsDiv.one('.submit-failed-header'); - if (failedHeader) { - // Already created. Return it. - return {header: failedHeader, message: controlsDiv.one('.submit-failed-message')}; - } - - // Needs to be created. - controlsDiv.insert('
    ', 0); - failedHeader = controlsDiv.one('.submit-failed-header'); - failedHeader.append('

    ' + M.util.get_string('submitfailed', 'quizaccess_wifiresilience') + '

    '); - failedHeader.append('

    ' + M.util.get_string('submitfailedmessage', 'quizaccess_wifiresilience') + '

    '); - - var downloadLink = '' + - M.util.get_string('savetheresponses', 'quizaccess_wifiresilience') + ''; - var failedMessage = controlsDiv.appendChild('
    '); - failedMessage.append( - '

    ' + M.util.get_string('submitfaileddownloadmessage', 'quizaccess_wifiresilience', downloadLink) + '

    '); - - return {header: failedHeader, message: failedMessage}; - }, - - create_status_messages: function() { - - var last_saved_msg_str = ''; - var saving_dots_str = ''; - - if (this.display_nav_details == 1) { - last_saved_msg_str = M.util.get_string('lastsaved', 'quizaccess_wifiresilience', ''); - saving_dots_str = M.util.get_string('savingdots', 'quizaccess_wifiresilience'); - } - - Y.one('#mod_quiz_navblock .content').append('
    ' + - '
    ' + last_saved_msg_str + '
    ' + - '
    ' + saving_dots_str + '
    ' + - '
    ' + - '
    '); - - this.save_start_time = new Date(); - this.update_status_for_successful_save(); - this.save_start_time = null; - }, - - exam_saving_time_pad: function(number) { - return number < 10 ? '0' + number : number; - }, - exam_localstorage_saving_status_str: function() { - - var latest_localstorage_timing = new Date(this.locally_stored_data.last_change); - if (this.display_nav_details == 1) { - - Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours()) + - ':' + this.exam_saving_time_pad(this.last_successful_save.getMinutes()) + - ':' + this.exam_saving_time_pad(this.last_successful_save.getSeconds()) + - M.util.get_string('localstorage', 'quizaccess_wifiresilience') + - this.exam_saving_time_pad(latest_localstorage_timing.getHours()) + - ':' + this.exam_saving_time_pad(latest_localstorage_timing.getMinutes()) + - ':' + this.exam_saving_time_pad(latest_localstorage_timing.getSeconds())); - } - }, - - update_status_for_successful_save: function() { - - function pad(number) { + }, + + get_submit_failed_message: function(controlsDiv) { + var failedHeader = controlsDiv.one('.submit-failed-header'); + if (failedHeader) { + // Already created. Return it. + return {header: failedHeader, message: controlsDiv.one('.submit-failed-message')}; + } + + // Needs to be created. + controlsDiv.insert('
    ', 0); + failedHeader = controlsDiv.one('.submit-failed-header'); + failedHeader.append('

    ' + M.util.get_string('submitfailed', 'quizaccess_wifiresilience') + '

    '); + failedHeader.append('

    ' + M.util.get_string('submitfailedmessage', 'quizaccess_wifiresilience') + '

    '); + + var downloadLink = '' + + M.util.get_string('savetheresponses', 'quizaccess_wifiresilience') + ''; + var failedMessage = controlsDiv.appendChild('
    '); + failedMessage.append( + '

    ' + M.util.get_string('submitfaileddownloadmessage', 'quizaccess_wifiresilience', downloadLink) + '

    '); + + return {header: failedHeader, message: failedMessage}; + }, + + create_status_messages: function() { + + var last_saved_msg_str = ''; + var saving_dots_str = ''; + + if (this.display_nav_details == 1) { + last_saved_msg_str = M.util.get_string('lastsaved', 'quizaccess_wifiresilience', ''); + saving_dots_str = M.util.get_string('savingdots', 'quizaccess_wifiresilience'); + } + + Y.one('#mod_quiz_navblock .content').append('
    ' + + '
    ' + last_saved_msg_str + '
    ' + + '
    ' + saving_dots_str + '
    ' + + '
    ' + + '
    '); + + this.save_start_time = new Date(); + this.update_status_for_successful_save(); + this.save_start_time = null; + }, + + exam_saving_time_pad: function(number) { return number < 10 ? '0' + number : number; - } - wifi_current_time = Date(); - this.last_successful_save = new Date(wifi_current_time); - this.last_successful_server_save_timestamp = new Date(wifi_current_time); - this.exam_localstorage_saving_status_str(); - - Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); - Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string('savingdots', 'quizaccess_wifiresilience')); - Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).hide(); - - this.locally_stored_data.last_save = this.last_successful_server_save_timestamp; - - var stringified_data = Y.JSON.stringify(this.locally_stored_data); - // IndexedDB or WebSQL. - M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data); - - this.save_start_time = null; - }, - - update_status_for_failed_save: function() { - if (this.display_nav_details == 1) { - Y.one(this.SELECTORS.LAST_SAVED_MESSAGE).setHTML( - M.util.get_string('lastsavedtotheserver', 'quizaccess_wifiresilience', - Y.one(this.SELECTORS.LAST_SAVED_TIME).get('outerHTML'))); + }, + exam_localstorage_saving_status_str: function() { + + var latest_localstorage_timing = new Date(this.locally_stored_data.last_change); + if (this.display_nav_details == 1) { + + Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getMinutes()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getSeconds()) + + M.util.get_string('localstorage', 'quizaccess_wifiresilience') + + this.exam_saving_time_pad(latest_localstorage_timing.getHours()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getMinutes()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getSeconds())); + } + }, + + update_status_for_successful_save: function() { + + function pad(number) { + return number < 10 ? '0' + number : number; + } + wifi_current_time = Date(); + this.last_successful_save = new Date(wifi_current_time); + this.last_successful_server_save_timestamp = new Date(wifi_current_time); + this.exam_localstorage_saving_status_str(); + Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); - - Y.one(this.SELECTORS.SAVING_NOTICE).setHTML( - M.util.get_string('savingtryagaindots', 'quizaccess_wifiresilience') + '
    '); - Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).show(); - } - }, - - try_to_restore_session: function() { - this.loginDialogue = new M.core.notification.info({ - id: 'quiz-relogin-dialogue', - width: '70%', - center: true, - modal: true, - visible: false, - draggable: false - }); - - this.loginDialogue.setStdModContent(Y.WidgetStdMod.HEADER, - '

    ' + - M.util.get_string('logindialogueheader', 'quizaccess_wifiresilience') + - '

    ', Y.WidgetStdMod.REPLACE); - this.loginDialogue.setStdModContent(Y.WidgetStdMod.BODY, - ''); - } - - // Try to set real offline time from SessionStorage for people who are refreshing the attempt. - if (typeof(Storage) !== "undefined") { - sessionStorage.setItem('wifiresilience-offline-' + this.attemptid, this.real_offline_time); - // Now insert in session storage for refresh in service worker :). - sessionStorage.setItem('wifiresilience-offline-lastpage-' + this.attemptid, M.quizaccess_wifiresilience.navigation.currentpage); - } - // Now Show a warning. - e.returnValue = M.util.get_string('changesmadereallygoaway', 'quizaccess_wifiresilience'); - return e.returnValue; - }, - - /** - * Handle a click on the submit and finish button. That is, show a confirm dialogue. - * - * @param {EventFacade} e The triggering event, if there is one. - */ - submit_and_finish_clicked: function(e) { - e.halt(true); - - var confirmationDialogue = new M.core.confirm({ - id: 'submit-confirmation', - width: '300px', - center: true, - modal: true, - visible: false, - draggable: false, - 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') - }); - - // The dialogue was submitted with a positive value indication. - confirmationDialogue.on('complete-yes', this.submit_and_finish, this); - confirmationDialogue.render().show(); - }, - - /** - * Handle the submit and finish button in the confirm dialogue being pressed. - * - * @param {EventFacade} e The triggering event, if there is one. - */ - submit_and_finish: function(e) { - e.preventDefault(); - e.stopPropagation(); - - // Preserve time for submission - // Todo: substract total offline time (qtype fileresponse, complie code etc). - if (this.final_submission_time == 0) { - this.final_submission_time = Math.round(new Date().getTime() / 1000) - 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'); - - if (typeof tinyMCE !== 'undefined') { - 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: false - }); - this.save_start_time = new Date(); - }, - - submit_done: function(transactionid, response) { - var result; - try { - result = Y.JSON.parse(response.responseText); - } catch (e) { - Y.log('Final Submission Failure Reason (Transaction: ' + transactionid + '): ' + response.responseText, 'debug', - '[Wifiresilience-SW] Sync'); - this.submit_failed(transactionid, response); - return; - } - - if (result.result !== 'OK') { - this.submit_failed(transactionid, response); - return; - } - this.real_connection_icon_status(true); - Y.log('Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]', 'debug', - '[Wifiresilience-SW] Sync'); - this.save_transaction = null; - this.dirty = false; - // Try to delete the local data records via promises :). - - Y.log('Deleting local records after successful exam..', 'debug', - '[Wifiresilience-SW] Sync'); - M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(); - - // Go to Exam Review URL. - window.location.replace(result.reviewurl); - }, - - submit_failed: function(transactionid = null, response = null) { - this.real_connection_icon_status(false); - Y.log('Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]', 'debug', - '[Wifiresilience-SW] Sync'); - this.save_transaction = null; - - // Re-display the submit button. - this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove(); - var submitButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); - var submitProgress = this.get_submit_progress(submitButton.ancestor('.controls')); - submitButton.ancestor('.singlebutton').show(); - submitProgress.hide(); - - // And show the failure message. - var failureMessage = this.get_submit_failed_message(submitButton.ancestor('.controls')); - submitButton.ancestor('.controls').addClass('quiz-save-failed'); - failureMessage.header.show(); - failureMessage.message.show(); - - this.update_status_for_failed_save(); - - // Change the label of submit again to "try again". - var submitAndFinishButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); - submitAndFinishButton.set('value',M.util.get_string('submitallandfinishtryagain', 'quizaccess_wifiresilience')); - - function response_is_json_string(str) { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - } - - if (response) { - if (response_is_json_string(response.responseText)) { // Moodle validation issues. - 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'); - } else { // Server issues. - nice_merror_message = "Server Connection Error."; - Y.log(response, 'debug', '[Wifiresilience-SW] Sync'); - } - } - if ($("#wifi_debug_exam_error_details")) { - $("#wifi_debug_exam_error_details").html(''); - } - var d = new Date(); - var whenhappened = d.toUTCString(); - - if (this.display_tech_errors == 1) { - $(".submit-failed-message").append("

    " + - whenhappened + - ": " + nice_merror_message + "
    "); - } - // Now do that sourceforge style automatic download. - var data = {responses: Y.IO.stringify(M.quizaccess_wifiresilience.download.form)}; - if (M.quizaccess_wifiresilience.download.publicKey) { - data = M.quizaccess_wifiresilience.download.encryptResponses(data); - } - var blob = new Blob([Y.JSON.stringify(data)], {type: "octet/stream"}); - var url = window.URL.createObjectURL(blob); - $(".submit-failed-message").append( - ''); - }, - - get_submit_progress: function(controlsDiv) { - var submitProgress = controlsDiv.one('.submit-progress'); - if (submitProgress) { - // Already created. Return it. - return submitProgress; - } - - // Needs to be created. - submitProgress = controlsDiv.appendChild('
    '); - M.util.add_spinner(Y, submitProgress).show(); - submitProgress.append(M.util.get_string('submitting', 'quizaccess_wifiresilience')); - return submitProgress; - }, - - get_submit_failed_message: function(controlsDiv) { - var failedHeader = controlsDiv.one('.submit-failed-header'); - if (failedHeader) { - // Already created. Return it. - return {header: failedHeader, message: controlsDiv.one('.submit-failed-message')}; - } - - // Needs to be created. - controlsDiv.insert('
    ', 0); - failedHeader = controlsDiv.one('.submit-failed-header'); - failedHeader.append('

    ' + M.util.get_string('submitfailed', 'quizaccess_wifiresilience') + '

    '); - failedHeader.append('

    ' + M.util.get_string('submitfailedmessage', 'quizaccess_wifiresilience') + '

    '); - - var downloadLink = '' + - M.util.get_string('savetheresponses', 'quizaccess_wifiresilience') + ''; - var failedMessage = controlsDiv.appendChild('
    '); - failedMessage.append( - '

    ' + M.util.get_string('submitfaileddownloadmessage', 'quizaccess_wifiresilience', downloadLink) + '

    '); - - return {header: failedHeader, message: failedMessage}; - }, - - create_status_messages: function() { - - var last_saved_msg_str = ''; - var saving_dots_str = ''; - - if (this.display_nav_details == 1) { - last_saved_msg_str = M.util.get_string('lastsaved', 'quizaccess_wifiresilience', ''); - saving_dots_str = M.util.get_string('savingdots', 'quizaccess_wifiresilience'); - } - - Y.one('#mod_quiz_navblock .content').append('
    ' + - '
    ' + last_saved_msg_str + '
    ' + - '
    ' + saving_dots_str + '
    ' + - '
    ' + - '
    '); - - this.save_start_time = new Date(); - this.update_status_for_successful_save(); - this.save_start_time = null; - }, - - exam_saving_time_pad: function(number) { - return number < 10 ? '0' + number : number; - }, - exam_localstorage_saving_status_str: function() { - - var latest_localstorage_timing = new Date(this.locally_stored_data.last_change); - if (this.display_nav_details == 1) { - - Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours()) + - ':' + this.exam_saving_time_pad(this.last_successful_save.getMinutes()) + - ':' + this.exam_saving_time_pad(this.last_successful_save.getSeconds()) + - M.util.get_string('localstorage', 'quizaccess_wifiresilience') + - this.exam_saving_time_pad(latest_localstorage_timing.getHours()) + - ':' + this.exam_saving_time_pad(latest_localstorage_timing.getMinutes()) + - ':' + this.exam_saving_time_pad(latest_localstorage_timing.getSeconds())); - } - }, - - update_status_for_successful_save: function() { - - function pad(number) { - return number < 10 ? '0' + number : number; - } - wifi_current_time = Date(); - this.last_successful_save = new Date(wifi_current_time); - this.last_successful_server_save_timestamp = new Date(wifi_current_time); - this.exam_localstorage_saving_status_str(); - - Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); - Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string('savingdots', 'quizaccess_wifiresilience')); - Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).hide(); - - this.locally_stored_data.last_save = this.last_successful_server_save_timestamp; - - var stringified_data = Y.JSON.stringify(this.locally_stored_data); - // IndexedDB or WebSQL. - M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data); - - this.save_start_time = null; - }, - - update_status_for_failed_save: function() { - if (this.display_nav_details == 1) { - Y.one(this.SELECTORS.LAST_SAVED_MESSAGE).setHTML( - M.util.get_string('lastsavedtotheserver', 'quizaccess_wifiresilience', - Y.one(this.SELECTORS.LAST_SAVED_TIME).get('outerHTML'))); - Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); - - Y.one(this.SELECTORS.SAVING_NOTICE).setHTML( - M.util.get_string('savingtryagaindots', 'quizaccess_wifiresilience') + '
    '); - Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).show(); - } - }, - - try_to_restore_session: function() { - this.loginDialogue = new M.core.notification.info({ - id: 'quiz-relogin-dialogue', - width: '70%', - center: true, - modal: true, - visible: false, - draggable: false - }); - - this.loginDialogue.setStdModContent(Y.WidgetStdMod.HEADER, - '

    ' + - M.util.get_string('logindialogueheader', 'quizaccess_wifiresilience') + - '

    ', Y.WidgetStdMod.REPLACE); - this.loginDialogue.setStdModContent(Y.WidgetStdMod.BODY, - ''); + } + + // Try to set real offline time from SessionStorage for people who are refreshing the attempt. + if (typeof(Storage) !== "undefined") { + sessionStorage.setItem('wifiresilience-offline-' + this.attemptid, this.real_offline_time); + // Now insert in session storage for refresh in service worker :). + sessionStorage.setItem('wifiresilience-offline-lastpage-' + this.attemptid, M.quizaccess_wifiresilience.navigation.currentpage); + } + // Now Show a warning. + e.returnValue = M.util.get_string('changesmadereallygoaway', 'quizaccess_wifiresilience'); + return e.returnValue; + }, + + /** + * Handle a click on the submit and finish button. That is, show a confirm dialogue. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish_clicked: function(e) { + e.halt(true); + + var confirmationDialogue = new M.core.confirm({ + id: 'submit-confirmation', + width: '300px', + center: true, + modal: true, + visible: false, + draggable: false, + 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') + }); + + // The dialogue was submitted with a positive value indication. + confirmationDialogue.on('complete-yes', this.submit_and_finish, this); + confirmationDialogue.render().show(); + }, + + /** + * Handle the submit and finish button in the confirm dialogue being pressed. + * + * @param {EventFacade} e The triggering event, if there is one. + */ + submit_and_finish: function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Preserve time for submission + // Todo: substract total offline time (qtype fileresponse, complie code etc). + if (this.final_submission_time == 0) { + this.final_submission_time = Math.round(new Date().getTime() / 1000) - 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'); + + if (typeof tinyMCE !== 'undefined') { + 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: false + }); + this.save_start_time = new Date(); + }, + + submit_done: function(transactionid, response) { + var result; + try { + result = Y.JSON.parse(response.responseText); + } catch (e) { + Y.log('Final Submission Failure Reason (Transaction: ' + transactionid + '): ' + response.responseText, 'debug', + '[Wifiresilience-SW] Sync'); + this.submit_failed(transactionid, response); + return; + } + + if (result.result !== 'OK') { + this.submit_failed(transactionid, response); + return; + } + this.real_connection_icon_status(true); + Y.log('Final Submit Successful, Retard! Retard! Retard! [Redirecting out of Exam..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + this.dirty = false; + // Try to delete the local data records via promises :). + + Y.log('Deleting local records after successful exam..', 'debug', + '[Wifiresilience-SW] Sync'); + M.quizaccess_wifiresilience.localforage.delete_records_after_successful_submission(); + + // Go to Exam Review URL. + window.location.replace(result.reviewurl); + }, + + submit_failed: function(transactionid = null, response = null) { + this.real_connection_icon_status(false); + Y.log('Final Submit failed. Emergency! Emergency! Emergency! [Offering to Download Responses..]', 'debug', + '[Wifiresilience-SW] Sync'); + this.save_transaction = null; + + // Re-display the submit button. + this.form.one(this.SELECTORS.FINISH_ATTEMPT_INPUT).remove(); + var submitButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + var submitProgress = this.get_submit_progress(submitButton.ancestor('.controls')); + submitButton.ancestor('.singlebutton').show(); + submitProgress.hide(); + + // And show the failure message. + var failureMessage = this.get_submit_failed_message(submitButton.ancestor('.controls')); + submitButton.ancestor('.controls').addClass('quiz-save-failed'); + failureMessage.header.show(); + failureMessage.message.show(); + + this.update_status_for_failed_save(); + + // Change the label of submit again to "try again". + var submitAndFinishButton = Y.one(this.SELECTORS.SUBMIT_BUTTON); + submitAndFinishButton.set('value',M.util.get_string('submitallandfinishtryagain', 'quizaccess_wifiresilience')); + + function response_is_json_string(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } + + if (response) { + if (response_is_json_string(response.responseText)) { // Moodle validation issues. + 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'); + } else { // Server issues. + nice_merror_message = "Server Connection Error."; + Y.log(response, 'debug', '[Wifiresilience-SW] Sync'); + } + } + if ($("#wifi_debug_exam_error_details")) { + $("#wifi_debug_exam_error_details").html(''); + } + var d = new Date(); + var whenhappened = d.toUTCString(); + + if (this.display_tech_errors == 1) { + $(".submit-failed-message").append("

    " + + whenhappened + + ": " + nice_merror_message + "
    "); + } + // Now do that sourceforge style automatic download. + var data = {responses: Y.IO.stringify(M.quizaccess_wifiresilience.download.form)}; + if (M.quizaccess_wifiresilience.download.publicKey) { + data = M.quizaccess_wifiresilience.download.encryptResponses(data); + } + var blob = new Blob([Y.JSON.stringify(data)], {type: "octet/stream"}); + var url = window.URL.createObjectURL(blob); + $(".submit-failed-message").append( + ''); + }, + + get_submit_progress: function(controlsDiv) { + var submitProgress = controlsDiv.one('.submit-progress'); + if (submitProgress) { + // Already created. Return it. + return submitProgress; + } + + // Needs to be created. + submitProgress = controlsDiv.appendChild('
    '); + M.util.add_spinner(Y, submitProgress).show(); + submitProgress.append(M.util.get_string('submitting', 'quizaccess_wifiresilience')); + return submitProgress; + }, + + get_submit_failed_message: function(controlsDiv) { + var failedHeader = controlsDiv.one('.submit-failed-header'); + if (failedHeader) { + // Already created. Return it. + return {header: failedHeader, message: controlsDiv.one('.submit-failed-message')}; + } + + // Needs to be created. + controlsDiv.insert('
    ', 0); + failedHeader = controlsDiv.one('.submit-failed-header'); + failedHeader.append('

    ' + M.util.get_string('submitfailed', 'quizaccess_wifiresilience') + '

    '); + failedHeader.append('

    ' + M.util.get_string('submitfailedmessage', 'quizaccess_wifiresilience') + '

    '); + + var downloadLink = '' + + M.util.get_string('savetheresponses', 'quizaccess_wifiresilience') + ''; + var failedMessage = controlsDiv.appendChild('
    '); + failedMessage.append( + '

    ' + M.util.get_string('submitfaileddownloadmessage', 'quizaccess_wifiresilience', downloadLink) + '

    '); + + return {header: failedHeader, message: failedMessage}; + }, + + create_status_messages: function() { + + var last_saved_msg_str = ''; + var saving_dots_str = ''; + + if (this.display_nav_details == 1) { + last_saved_msg_str = M.util.get_string('lastsaved', 'quizaccess_wifiresilience', ''); + saving_dots_str = M.util.get_string('savingdots', 'quizaccess_wifiresilience'); + } + + Y.one('#mod_quiz_navblock .content').append('
    ' + + '
    ' + last_saved_msg_str + '
    ' + + '
    ' + saving_dots_str + '
    ' + + '
    ' + + '
    '); + + this.save_start_time = new Date(); + this.update_status_for_successful_save(); + this.save_start_time = null; + }, + + exam_saving_time_pad: function(number) { + return number < 10 ? '0' + number : number; + }, + exam_localstorage_saving_status_str: function() { + + var latest_localstorage_timing = new Date(this.locally_stored_data.last_change); + if (this.display_nav_details == 1) { + + Y.one(this.SELECTORS.LAST_SAVED_TIME).setHTML(this.exam_saving_time_pad(this.last_successful_save.getHours()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getMinutes()) + + ':' + this.exam_saving_time_pad(this.last_successful_save.getSeconds()) + + M.util.get_string('localstorage', 'quizaccess_wifiresilience') + + this.exam_saving_time_pad(latest_localstorage_timing.getHours()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getMinutes()) + + ':' + this.exam_saving_time_pad(latest_localstorage_timing.getSeconds())); + } + }, + + update_status_for_successful_save: function() { + + function pad(number) { + return number < 10 ? '0' + number : number; + } + wifi_current_time = Date(); + this.last_successful_save = new Date(wifi_current_time); + this.last_successful_server_save_timestamp = new Date(wifi_current_time); + this.exam_localstorage_saving_status_str(); + + Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); + Y.one(this.SELECTORS.SAVING_NOTICE).setHTML(M.util.get_string('savingdots', 'quizaccess_wifiresilience')); + Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).hide(); + + this.locally_stored_data.last_save = this.last_successful_server_save_timestamp; + + var stringified_data = Y.JSON.stringify(this.locally_stored_data); + // IndexedDB or WebSQL. + M.quizaccess_wifiresilience.localforage.save_status_records(stringified_data); + + this.save_start_time = null; + }, + + update_status_for_failed_save: function() { + if (this.display_nav_details == 1) { + Y.one(this.SELECTORS.LAST_SAVED_MESSAGE).setHTML( + M.util.get_string('lastsavedtotheserver', 'quizaccess_wifiresilience', + Y.one(this.SELECTORS.LAST_SAVED_TIME).get('outerHTML'))); + Y.one(this.SELECTORS.SAVING_NOTICE).setStyle('display', 'none'); + + Y.one(this.SELECTORS.SAVING_NOTICE).setHTML( + M.util.get_string('savingtryagaindots', 'quizaccess_wifiresilience') + '
    '); + Y.one(this.SELECTORS.SAVE_FAILED_NOTICE).show(); + } + }, + + try_to_restore_session: function() { + this.loginDialogue = new M.core.notification.info({ + id: 'quiz-relogin-dialogue', + width: '70%', + center: true, + modal: true, + visible: false, + draggable: false + }); + + this.loginDialogue.setStdModContent(Y.WidgetStdMod.HEADER, + '

    ' + + M.util.get_string('logindialogueheader', 'quizaccess_wifiresilience') + + '

    ', Y.WidgetStdMod.REPLACE); + this.loginDialogue.setStdModContent(Y.WidgetStdMod.BODY, + '