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();