From 6bf267776c1117e23403e88720872cd5429cccb9 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 24 Mar 2014 19:28:59 +0800 Subject: [PATCH] MDL-28261 Javascript: Check for a working connection in SCORM activities --- lang/en/moodle.php | 2 + .../moodle-core-checknet/assets/checknet.txt | 1 + .../moodle-core-checknet-debug.js | 194 ++++++++++++++++++ .../moodle-core-checknet-min.js | 1 + .../moodle-core-checknet.js | 191 +++++++++++++++++ lib/yui/src/checknet/assets/checknet.txt | 1 + lib/yui/src/checknet/build.json | 10 + lib/yui/src/checknet/js/checknet.js | 189 +++++++++++++++++ lib/yui/src/checknet/meta/checknet.json | 9 + mod/scorm/lang/en/scorm.php | 2 + mod/scorm/player.php | 6 + 11 files changed, 606 insertions(+) create mode 100644 lib/yui/build/moodle-core-checknet/assets/checknet.txt create mode 100644 lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js create mode 100644 lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js create mode 100644 lib/yui/build/moodle-core-checknet/moodle-core-checknet.js create mode 100644 lib/yui/src/checknet/assets/checknet.txt create mode 100644 lib/yui/src/checknet/build.json create mode 100644 lib/yui/src/checknet/js/checknet.js create mode 100644 lib/yui/src/checknet/meta/checknet.json diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 2e01aab0667..a3b7cf819ea 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1210,6 +1210,8 @@ $string['nameforlink'] = 'What do you want to call this link?'; $string['nameforpage'] = 'Name'; $string['navigation'] = 'Navigation'; $string['needed'] = 'Needed'; +$string['networkdropped'] = 'We have detected that your Internet connection is unreliable or has been interrupted.
+Please be aware that changes may not be saved properly until your connection improves.'; $string['never'] = 'Never'; $string['neverdeletelogs'] = 'Never delete logs'; $string['new'] = 'New'; diff --git a/lib/yui/build/moodle-core-checknet/assets/checknet.txt b/lib/yui/build/moodle-core-checknet/assets/checknet.txt new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/lib/yui/build/moodle-core-checknet/assets/checknet.txt @@ -0,0 +1 @@ +1 diff --git a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js new file mode 100644 index 00000000000..f5269612a24 --- /dev/null +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js @@ -0,0 +1,194 @@ +YUI.add('moodle-core-checknet', function (Y, NAME) { + +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * A utility to check whether the connection to the Moodle server is still + * active. + * + * @module moodle-core-checknet + * @package core + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @main moodle-core-checknet + */ + +/** + * @namespace M.core + * @class checknet + */ + +function CheckNet() { + CheckNet.superclass.constructor.apply(this, arguments); +} + +Y.extend(CheckNet, Y.Base, { + /** + * A link to the warning dialogue. + * + * @property _alertDialogue + * @type M.core.dialogue + * @private + * @default null + */ + _alertDialogue: null, + + /** + * Setup the checking mechanism. + * + * @method initializer + */ + initializer: function() { + // Perform our first check. + this._scheduleCheck(); + }, + + /** + * Schedule a check of the checknet file. + * + * @method _scheduleCheck + * @chainable + * @private + */ + _scheduleCheck: function() { + // Schedule the next check after five seconds. + Y.later(this.get('frequency'), this, this._performCheck); + + return this; + }, + + /** + * Perform an immediate check of the checknet file. + * + * @method _performCheck + * @private + */ + _performCheck: function() { + Y.io(this.get('uri'), { + data: { + // Add a query string to prevent older versions of IE from using the cache. + time: new Date().getTime() + }, + timeout: this.get('timeout'), + headers: { + 'Cache-Control': 'no-cache', + 'Expires': '-1' + }, + context: this, + on: { + complete: function(tid, response) { + // Check for failure conditions. + // We check for a valid status here because if the user is moving away from the page at the time we + // run this callback we do not want to display the error. + if (response && typeof response.status !== "undefined") { + var code = parseInt(response.status, 10); + + if (code === 200) { + // This is a valid attempt - clear any existing warning dialogue and destroy it. + if (this._alertDialogue) { + this._alertDialogue.destroy(); + this._alertDialogue = null; + } + } else if (code >= 300 && code <= 399) { + // This is a cached status - warn developers, but otherwise ignore. + Y.log("A cached copy of the checknet status file was returned so it's reliablity cannot be guaranteed", + 'warn', + 'moodle-mod_scorm-checknet'); + } else { + if (this._alertDialogue === null || this._alertDialogue.get('destroyed')) { + // Only create a new dialogue if it isn't already displayed. + this._alertDialogue = new M.core.alert({ + message: M.util.get_string.apply(this, this.get('message')) + }); + } else { + this._alertDialogue.show(); + } + } + } + + // Start the next check. + this._scheduleCheck(); + } + } + }); + } +}, { + NAME: 'checkNet', + ATTRS: { + /** + * The file to check access against. + * + * @attribute uri + * @type String + * @default M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + */ + uri: { + value: M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + }, + + /** + * The timeout (in milliseconds) before the checker should give up and display a warning. + * + * @attribute timeout + * @type Number + * @value 2000 + */ + timeout: { + value: 2000 + }, + + /** + * The frequency (in milliseconds) that checks should be run. + * A new check is not begun until the previous check has completed. + * + * @attribute frequency + * @writeOnce + * @type Number + * @value 5000 + */ + frequency: { + value: 5000 + }, + + /** + * The message which should be displayed upon a test failure. + * + * The array values are passed directly to M.util.get_string() and arguments should match accordingly. + * + * @attribute message + * @type Array + * @value [ + * 'networkdropped', + * 'moodle' + * ] + */ + message: { + value: [ + 'networkdropped', + 'moodle' + ] + } + } +}); + +M.core = M.core || {}; +M.core.checknet = M.core.checknet || {}; +M.core.checknet.init = function(config) { + return new CheckNet(config); +}; + + +}, '@VERSION@', {"requires": ["base-base", "moodle-core-notification-alert", "io-base"]}); diff --git a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js new file mode 100644 index 00000000000..f29d34998b3 --- /dev/null +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js @@ -0,0 +1 @@ +YUI.add("moodle-core-checknet",function(e,t){function n(){n.superclass.constructor.apply(this,arguments)}e.extend(n,e.Base,{_alertDialogue:null,initializer:function(){this._scheduleCheck()},_scheduleCheck:function(){return e.later(this.get("frequency"),this,this._performCheck),this},_performCheck:function(){e.io(this.get("uri"),{data:{time:(new Date).getTime()},timeout:this.get("timeout"),headers:{"Cache-Control":"no-cache",Expires:"-1"},context:this,on:{complete:function(e,t){if(t&&typeof t.status!="undefined"){var n=parseInt(t.status,10);n===200?this._alertDialogue&&(this._alertDialogue.destroy(),this._alertDialogue=null):n>=300&&n<=399||(this._alertDialogue===null||this._alertDialogue.get("destroyed")?this._alertDialogue=new M.core.alert({message:M.util.get_string.apply(this,this.get("message"))}):this._alertDialogue.show())}this._scheduleCheck()}}})}},{NAME:"checkNet",ATTRS:{uri:{value:M.cfg.wwwroot+"/lib/yui/build/moodle-core-checknet/assets/checknet.txt"},timeout:{value:2e3},frequency:{value:5e3},message:{value:["networkdropped","moodle"]}}}),M.core=M.core||{},M.core.checknet=M.core.checknet||{},M.core.checknet.init=function(e){return new n(e)}},"@VERSION@",{requires:["base-base","moodle-core-notification-alert","io-base"]}); diff --git a/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js b/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js new file mode 100644 index 00000000000..f2e005e0369 --- /dev/null +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js @@ -0,0 +1,191 @@ +YUI.add('moodle-core-checknet', function (Y, NAME) { + +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * A utility to check whether the connection to the Moodle server is still + * active. + * + * @module moodle-core-checknet + * @package core + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @main moodle-core-checknet + */ + +/** + * @namespace M.core + * @class checknet + */ + +function CheckNet() { + CheckNet.superclass.constructor.apply(this, arguments); +} + +Y.extend(CheckNet, Y.Base, { + /** + * A link to the warning dialogue. + * + * @property _alertDialogue + * @type M.core.dialogue + * @private + * @default null + */ + _alertDialogue: null, + + /** + * Setup the checking mechanism. + * + * @method initializer + */ + initializer: function() { + // Perform our first check. + this._scheduleCheck(); + }, + + /** + * Schedule a check of the checknet file. + * + * @method _scheduleCheck + * @chainable + * @private + */ + _scheduleCheck: function() { + // Schedule the next check after five seconds. + Y.later(this.get('frequency'), this, this._performCheck); + + return this; + }, + + /** + * Perform an immediate check of the checknet file. + * + * @method _performCheck + * @private + */ + _performCheck: function() { + Y.io(this.get('uri'), { + data: { + // Add a query string to prevent older versions of IE from using the cache. + time: new Date().getTime() + }, + timeout: this.get('timeout'), + headers: { + 'Cache-Control': 'no-cache', + 'Expires': '-1' + }, + context: this, + on: { + complete: function(tid, response) { + // Check for failure conditions. + // We check for a valid status here because if the user is moving away from the page at the time we + // run this callback we do not want to display the error. + if (response && typeof response.status !== "undefined") { + var code = parseInt(response.status, 10); + + if (code === 200) { + // This is a valid attempt - clear any existing warning dialogue and destroy it. + if (this._alertDialogue) { + this._alertDialogue.destroy(); + this._alertDialogue = null; + } + } else if (code >= 300 && code <= 399) { + // This is a cached status - warn developers, but otherwise ignore. + } else { + if (this._alertDialogue === null || this._alertDialogue.get('destroyed')) { + // Only create a new dialogue if it isn't already displayed. + this._alertDialogue = new M.core.alert({ + message: M.util.get_string.apply(this, this.get('message')) + }); + } else { + this._alertDialogue.show(); + } + } + } + + // Start the next check. + this._scheduleCheck(); + } + } + }); + } +}, { + NAME: 'checkNet', + ATTRS: { + /** + * The file to check access against. + * + * @attribute uri + * @type String + * @default M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + */ + uri: { + value: M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + }, + + /** + * The timeout (in milliseconds) before the checker should give up and display a warning. + * + * @attribute timeout + * @type Number + * @value 2000 + */ + timeout: { + value: 2000 + }, + + /** + * The frequency (in milliseconds) that checks should be run. + * A new check is not begun until the previous check has completed. + * + * @attribute frequency + * @writeOnce + * @type Number + * @value 5000 + */ + frequency: { + value: 5000 + }, + + /** + * The message which should be displayed upon a test failure. + * + * The array values are passed directly to M.util.get_string() and arguments should match accordingly. + * + * @attribute message + * @type Array + * @value [ + * 'networkdropped', + * 'moodle' + * ] + */ + message: { + value: [ + 'networkdropped', + 'moodle' + ] + } + } +}); + +M.core = M.core || {}; +M.core.checknet = M.core.checknet || {}; +M.core.checknet.init = function(config) { + return new CheckNet(config); +}; + + +}, '@VERSION@', {"requires": ["base-base", "moodle-core-notification-alert", "io-base"]}); diff --git a/lib/yui/src/checknet/assets/checknet.txt b/lib/yui/src/checknet/assets/checknet.txt new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/lib/yui/src/checknet/assets/checknet.txt @@ -0,0 +1 @@ +1 diff --git a/lib/yui/src/checknet/build.json b/lib/yui/src/checknet/build.json new file mode 100644 index 00000000000..406832d6ce1 --- /dev/null +++ b/lib/yui/src/checknet/build.json @@ -0,0 +1,10 @@ +{ + "name": "moodle-core-checknet", + "builds": { + "moodle-core-checknet": { + "jsfiles": [ + "checknet.js" + ] + } + } +} diff --git a/lib/yui/src/checknet/js/checknet.js b/lib/yui/src/checknet/js/checknet.js new file mode 100644 index 00000000000..ee37fb5c57d --- /dev/null +++ b/lib/yui/src/checknet/js/checknet.js @@ -0,0 +1,189 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * A utility to check whether the connection to the Moodle server is still + * active. + * + * @module moodle-core-checknet + * @package core + * @copyright 2014 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @main moodle-core-checknet + */ + +/** + * @namespace M.core + * @class checknet + */ + +function CheckNet() { + CheckNet.superclass.constructor.apply(this, arguments); +} + +Y.extend(CheckNet, Y.Base, { + /** + * A link to the warning dialogue. + * + * @property _alertDialogue + * @type M.core.dialogue + * @private + * @default null + */ + _alertDialogue: null, + + /** + * Setup the checking mechanism. + * + * @method initializer + */ + initializer: function() { + // Perform our first check. + this._scheduleCheck(); + }, + + /** + * Schedule a check of the checknet file. + * + * @method _scheduleCheck + * @chainable + * @private + */ + _scheduleCheck: function() { + // Schedule the next check after five seconds. + Y.later(this.get('frequency'), this, this._performCheck); + + return this; + }, + + /** + * Perform an immediate check of the checknet file. + * + * @method _performCheck + * @private + */ + _performCheck: function() { + Y.io(this.get('uri'), { + data: { + // Add a query string to prevent older versions of IE from using the cache. + time: new Date().getTime() + }, + timeout: this.get('timeout'), + headers: { + 'Cache-Control': 'no-cache', + 'Expires': '-1' + }, + context: this, + on: { + complete: function(tid, response) { + // Check for failure conditions. + // We check for a valid status here because if the user is moving away from the page at the time we + // run this callback we do not want to display the error. + if (response && typeof response.status !== "undefined") { + var code = parseInt(response.status, 10); + + if (code === 200) { + // This is a valid attempt - clear any existing warning dialogue and destroy it. + if (this._alertDialogue) { + this._alertDialogue.destroy(); + this._alertDialogue = null; + } + } else if (code >= 300 && code <= 399) { + // This is a cached status - warn developers, but otherwise ignore. + Y.log("A cached copy of the checknet status file was returned so it's reliablity cannot be guaranteed", + 'warn', + 'moodle-mod_scorm-checknet'); + } else { + if (this._alertDialogue === null || this._alertDialogue.get('destroyed')) { + // Only create a new dialogue if it isn't already displayed. + this._alertDialogue = new M.core.alert({ + message: M.util.get_string.apply(this, this.get('message')) + }); + } else { + this._alertDialogue.show(); + } + } + } + + // Start the next check. + this._scheduleCheck(); + } + } + }); + } +}, { + NAME: 'checkNet', + ATTRS: { + /** + * The file to check access against. + * + * @attribute uri + * @type String + * @default M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + */ + uri: { + value: M.cfg.wwwroot + '/lib/yui/build/moodle-core-checknet/assets/checknet.txt' + }, + + /** + * The timeout (in milliseconds) before the checker should give up and display a warning. + * + * @attribute timeout + * @type Number + * @value 2000 + */ + timeout: { + value: 2000 + }, + + /** + * The frequency (in milliseconds) that checks should be run. + * A new check is not begun until the previous check has completed. + * + * @attribute frequency + * @writeOnce + * @type Number + * @value 5000 + */ + frequency: { + value: 5000 + }, + + /** + * The message which should be displayed upon a test failure. + * + * The array values are passed directly to M.util.get_string() and arguments should match accordingly. + * + * @attribute message + * @type Array + * @value [ + * 'networkdropped', + * 'moodle' + * ] + */ + message: { + value: [ + 'networkdropped', + 'moodle' + ] + } + } +}); + +M.core = M.core || {}; +M.core.checknet = M.core.checknet || {}; +M.core.checknet.init = function(config) { + return new CheckNet(config); +}; diff --git a/lib/yui/src/checknet/meta/checknet.json b/lib/yui/src/checknet/meta/checknet.json new file mode 100644 index 00000000000..a63efb02920 --- /dev/null +++ b/lib/yui/src/checknet/meta/checknet.json @@ -0,0 +1,9 @@ +{ + "moodle-core-checknet": { + "requires": [ + "base-base", + "moodle-core-notification-alert", + "io-base" + ] + } +} diff --git a/mod/scorm/lang/en/scorm.php b/mod/scorm/lang/en/scorm.php index 65a4a0a2148..6592c2e2883 100644 --- a/mod/scorm/lang/en/scorm.php +++ b/mod/scorm/lang/en/scorm.php @@ -250,6 +250,8 @@ There are 3 options: $string['navdesc'] = 'This setting specifies whether to show or hide navigation buttons and their position.'; $string['navpositionleft'] = 'Position of navigation buttons from left in pixels.'; $string['navpositiontop'] = 'Position of navigation buttons from top in pixels.'; +$string['networkdropped'] = 'The SCORM player has determined that your Internet connection is unreliable or has been interrupted. If you continue in this SCORM activity, your progress may not be saved.
+You should exit the activity now, and return when you have a dependable Internet connection.'; $string['newattempt'] = 'Start a new attempt'; $string['next'] = 'Continue'; $string['noactivity'] = 'Nothing to report'; diff --git a/mod/scorm/player.php b/mod/scorm/player.php index bc584a179c0..e2da89171c6 100644 --- a/mod/scorm/player.php +++ b/mod/scorm/player.php @@ -257,4 +257,10 @@ if (empty($scorm->popup) || $displaymode == 'popup') { if (!empty($forcejs)) { echo $OUTPUT->box(get_string("forcejavascriptmessage", "scorm"), "generalbox boxaligncenter forcejavascriptmessage"); } + +// Add the checknet system to keep checking for a connection. +$PAGE->requires->string_for_js('networkdropped', 'mod_scorm'); +$PAGE->requires->yui_module('moodle-core-checknet', 'M.core.checknet.init', array(array( + 'message' => array('networkdropped', 'mod_scorm'), +))); echo $OUTPUT->footer();