diff --git a/grade/grading/form/rubric/edit.php b/grade/grading/form/rubric/edit.php
index c1849f2a268..15bda1e6f96 100644
--- a/grade/grading/form/rubric/edit.php
+++ b/grade/grading/form/rubric/edit.php
@@ -65,6 +65,10 @@ if ($mform->is_cancelled()) {
redirect($returnurl, $warning, null, \core\output\notification::NOTIFY_ERROR);
}
+// Try to keep the session alive on this page as it may take some time
+// before significant interaction happens with the server.
+\core\session\manager::keepalive();
+
echo $OUTPUT->header();
$mform->display();
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
diff --git a/lang/en/error.php b/lang/en/error.php
index 825a6b35294..e0b5519f08a 100644
--- a/lang/en/error.php
+++ b/lang/en/error.php
@@ -496,6 +496,7 @@ $string['sendmessage'] = 'Send message';
$string['serverconnection'] = 'Error connecting to the server';
$string['servicedonotexist'] = 'The service does not exist';
$string['sessionwaiterr'] = 'Timed out while waiting for session lock.
Wait for your current requests to finish and try again later.';
+$string['sessionexpired'] = 'Session expired';
$string['sessioncookiesdisable'] = 'Incorrect use of require_key_login() - session cookies must be disabled!';
$string['sessiondiskfull'] = 'The session partition is full. It is not possible to log in at this time. Please notify the server administrator.';
$string['sessionhandlerproblem'] = 'Session handler is misconfigured';
diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index df841d662f3..6c3294b8b11 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -802,6 +802,7 @@ $string['existingcoursedeleting'] = 'Existing course, deleting it first';
$string['existingcreators'] = 'Existing course creators';
$string['existingstudents'] = 'Enrolled students';
$string['existingteachers'] = 'Existing teachers';
+$string['extendsession'] = 'Extend session';
$string['expand'] = 'Expand';
$string['expandall'] = 'Expand all';
$string['expandcategory'] = 'Expand {$a}';
@@ -1807,7 +1808,8 @@ $string['separateandconnected'] = 'Separate and Connected ways of knowing';
$string['separateandconnectedinfo'] = 'The scale based on the theory of separate and connected knowing. This theory describes two different ways that we can evaluate and learn about the things we see and hear.
- Separate knowers remain as objective as possible without including feelings and emotions. In a discussion with other people, they like to defend their own ideas, using logic to find holes in opponent\'s ideas.
- Connected knowers are more sensitive to other people. They are skilled at empathy and tend to listen and ask questions until they feel they can connect and "understand things from their point of view". They learn by trying to share the experiences that led to the knowledge they find in other people.
';
$string['servererror'] = 'An error occurred whilst communicating with the server';
$string['serverlocaltime'] = 'Server\'s local time';
-$string['sessionforceclean'] = 'As a security precaution, user-generated scripts have been disabled within this session.';
+$string['sessionforceclean'] = 'As a security precaution, user-generated scripts have been disabled within this session';
+$string['sessiontimeoutsoon'] = 'Your session is about to timeout. Do you want to extend your current session?';
$string['setcategorytheme'] = 'Set category theme';
$string['setpassword'] = 'Set password';
$string['setpasswordinstructions'] = 'Please enter your new password below, then save changes.';
diff --git a/lib/ajax/service.php b/lib/ajax/service.php
index 3298c0e7a75..caaf86c66f9 100644
--- a/lib/ajax/service.php
+++ b/lib/ajax/service.php
@@ -29,6 +29,10 @@
define('AJAX_SCRIPT', true);
+if (!empty($_GET['nosessionupdate'])) {
+ define('NO_SESSION_UPDATE', true);
+}
+
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/externallib.php');
@@ -63,5 +67,4 @@ foreach ($requests as $request) {
break;
}
}
-
echo json_encode($responses);
diff --git a/lib/amd/build/ajax.min.js b/lib/amd/build/ajax.min.js
index f8c54537985..adc66eb5b3a 100644
--- a/lib/amd/build/ajax.min.js
+++ b/lib/amd/build/ajax.min.js
@@ -1 +1 @@
-define(["jquery","core/config","core/log","core/url"],function(a,b,c,d){var e=!1,f=function(a){var b,c,e=this,f=null,g=0;if(a.error)for(;g0&&setTimeout(o,h),!0}).fail(function(){d.alert("",j)})},p=function(){var a={methodname:"core_session_time_remaining",args:{}};return k=!1,b.call([a],!0,!0,!0)[0].then(function(a){return!(a.userid<=0)&&(a.timeremaining<0?e.get_strings([{key:"sessionexpired",component:"error"},{key:"sessionerroruser",component:"error"}]).then(function(a){return d.alert(a[0],a[1]),!0}).fail(d.exception):1e3*a.timeremaining0?setTimeout(o,h):setTimeout(p,5*l)},r=function(){f||(f=!0,q())},s=function(a,b,c){f||(f=!0,h=1e3*a,j=c,i=1e3*b,q())};return{keepalive:s,init:r}});
\ No newline at end of file
diff --git a/lib/amd/build/page_global.min.js b/lib/amd/build/page_global.min.js
index 838ab91a5f5..a02a46c08e8 100644
--- a/lib/amd/build/page_global.min.js
+++ b/lib/amd/build/page_global.min.js
@@ -1 +1 @@
-define(["jquery","core/custom_interaction_events","core/str"],function(a,b,c){var d=function(){var d=a("body");b.define(d,[b.events.activate]),d.on(b.events.activate,"[data-show-active-item]",function(b){var d=a(b.target).closest(".dropdown-item"),e=d.closest("[data-show-active-item]");if(d.hasClass("dropdown-item")&&!d.hasClass("active")){var f=e.find(".dropdown-item");f.removeClass("active"),f.removeAttr("aria-current"),e.attr("data-skip-active-class")||d.addClass("active"),d.attr("aria-current",!0);var g=d.text(),h=e.parent().find('[data-toggle="dropdown"]'),i=h.find("[data-active-item-text]");i.length?i.html(g):h.html(g);var j=e.attr("data-active-item-button-aria-label-components");if(j){var k=j.split(",");k.push(g),c.get_string(k[0].trim(),k[1].trim(),k[2].trim()).then(function(a){return h.attr("aria-label",a),a})["catch"](function(){return!1})}}})},e=function(){d()};return{init:e}});
\ No newline at end of file
+define(["jquery","core/custom_interaction_events","core/str","core/network"],function(a,b,c,d){var e=function(){var d=a("body");b.define(d,[b.events.activate]),d.on(b.events.activate,"[data-show-active-item]",function(b){var d=a(b.target).closest(".dropdown-item"),e=d.closest("[data-show-active-item]");if(d.hasClass("dropdown-item")&&!d.hasClass("active")){var f=e.find(".dropdown-item");f.removeClass("active"),f.removeAttr("aria-current"),e.attr("data-skip-active-class")||d.addClass("active"),d.attr("aria-current",!0);var g=d.text(),h=e.parent().find('[data-toggle="dropdown"]'),i=h.find("[data-active-item-text]");i.length?i.html(g):h.html(g);var j=e.attr("data-active-item-button-aria-label-components");if(j){var k=j.split(",");k.push(g),c.get_string(k[0].trim(),k[1].trim(),k[2].trim()).then(function(a){return h.attr("aria-label",a),a})["catch"](function(){return!1})}}})},f=function(){e(),d.init()};return{init:f}});
\ No newline at end of file
diff --git a/lib/amd/src/ajax.js b/lib/amd/src/ajax.js
index a13cbcf2cda..98e982c5ee4 100644
--- a/lib/amd/src/ajax.js
+++ b/lib/amd/src/ajax.js
@@ -40,11 +40,12 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
*/
var requestSuccess = function(responses) {
// Call each of the success handlers.
- var requests = this;
- var exception = null;
- var i = 0;
- var request;
- var response;
+ var requests = this,
+ exception = null,
+ i = 0,
+ request,
+ response,
+ nosessionupdate;
if (responses.error) {
// There was an error with the request as a whole.
@@ -69,6 +70,7 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
request.deferred.resolve(response.data);
} else {
exception = response.exception;
+ nosessionupdate = requests[i].nosessionupdate;
break;
}
} else {
@@ -80,7 +82,7 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
// Something failed, reject the remaining promises.
if (exception !== null) {
// Redirect to the login page.
- if (exception.errorcode === "servicerequireslogin") {
+ if (exception.errorcode === "servicerequireslogin" && !nosessionupdate) {
window.location = URL.relativeUrl("/login/index.php");
} else {
requests.forEach(function(request) {
@@ -132,9 +134,12 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
* If false - this function will call the faster nologin ajax script - but
* will fail unless all functions have been marked as 'loginrequired' => false
* in services.php
+ * @param {Boolean} nosessionupdate Optional, defaults to false.
+ * If true, the timemodified for the session will not be updated.
+ * @param {Integer} timeout number of milliseconds to wait for a response. Defaults to no limit.
* @return {Promise[]} Array of promises that will be resolved when the ajax call returns.
*/
- call: function(requests, async, loginrequired) {
+ call: function(requests, async, loginrequired, nosessionupdate, timeout) {
$(window).bind('beforeunload', function() {
unloading = true;
});
@@ -150,6 +155,13 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
if (typeof async === "undefined") {
async = true;
}
+ if (typeof timeout === 'undefined') {
+ timeout = 0;
+ }
+
+ if (typeof nosessionupdate === "undefined") {
+ nosessionupdate = false;
+ }
for (i = 0; i < requests.length; i++) {
var request = requests[i];
ajaxRequestData.push({
@@ -157,6 +169,7 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
methodname: request.methodname,
args: request.args
});
+ request.nosessionupdate = nosessionupdate;
request.deferred = $.Deferred();
promises.push(request.deferred.promise());
// Allow setting done and fail handlers as arguments.
@@ -185,7 +198,8 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
dataType: 'json',
processData: false,
async: async,
- contentType: "application/json"
+ contentType: "application/json",
+ timeout: timeout
};
var script = 'service.php';
@@ -195,6 +209,10 @@ define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Lo
var url = config.wwwroot + '/lib/ajax/' + script +
'?sesskey=' + config.sesskey + '&info=' + requestInfo;
+ if (nosessionupdate) {
+ url += '&nosessionupdate=true';
+ }
+
// Jquery deprecated done and fail with async=false so we need to do this 2 ways.
if (async) {
$.ajax(url, settings)
diff --git a/lib/amd/src/network.js b/lib/amd/src/network.js
new file mode 100644
index 00000000000..5e21a6a6fba
--- /dev/null
+++ b/lib/amd/src/network.js
@@ -0,0 +1,196 @@
+// 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 .
+
+/**
+ * Poll the server to keep the session alive.
+ *
+ * @module core/network
+ * @package core
+ * @copyright 2019 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
+ function($, Ajax, Config, Notification, Str) {
+
+ var started = false;
+ var warningDisplayed = false;
+ var keepAliveFrequency = 0;
+ var requestTimeout = 0;
+ var keepAliveMessage = false;
+ var sessionTimeout = false;
+ // 1/10 of session timeout, max of 10 minutes.
+ var checkFrequency = Math.min((Config.sessiontimeout / 10), 600) * 1000;
+ // 1/5 of sessiontimeout.
+ var warningLimit = checkFrequency * 2;
+
+ /**
+ * The session time has expired - we can't extend it now.
+ */
+ var timeoutSessionExpired = function() {
+ sessionTimeout = true;
+ };
+
+ /**
+ * Ping the server to keep the session alive.
+ *
+ * @return {Promise}
+ */
+ var touchSession = function() {
+ var request = {
+ methodname: 'core_session_touch',
+ args: { }
+ };
+
+ if (sessionTimeout) {
+ // We timed out before we extended the session.
+ return Str.get_strings([
+ {key: 'sessionexpired', component: 'error'},
+ {key: 'sessionerroruser', component: 'error'}
+ ]).then(function(strings) {
+ Notification.alert(
+ strings[0], // Title.
+ strings[1] // Message.
+ );
+ return true;
+ }).fail(Notification.exception);
+ } else {
+ return Ajax.call([request], true, true, false, requestTimeout)[0].then(function() {
+ if (keepAliveFrequency > 0) {
+ setTimeout(touchSession, keepAliveFrequency);
+ }
+ return true;
+ }).fail(function() {
+ Notification.alert('', keepAliveMessage);
+ });
+ }
+ };
+
+ /**
+ * Ask the server how much time is remaining in this session and
+ * show confirm/cancel notifications if the session is about to run out.
+ *
+ * @return {Promise}
+ */
+ var checkSession = function() {
+ var request = {
+ methodname: 'core_session_time_remaining',
+ args: { }
+ };
+
+ sessionTimeout = false;
+ return Ajax.call([request], true, true, true)[0].then(function(args) {
+ if (args.userid <= 0) {
+ return false;
+ }
+ if (args.timeremaining < 0) {
+ Str.get_strings([
+ {key: 'sessionexpired', component: 'error'},
+ {key: 'sessionerroruser', component: 'error'}
+ ]).then(function(strings) {
+ Notification.alert(
+ strings[0], // Title.
+ strings[1] // Message.
+ );
+ return true;
+ }).fail(Notification.exception);
+
+ } else if (args.timeremaining * 1000 < warningLimit && !warningDisplayed) {
+ // If we don't extend the session before the timeout - warn.
+ setTimeout(timeoutSessionExpired, args.timeremaining * 1000);
+ warningDisplayed = true;
+ Str.get_strings([
+ {key: 'norecentactivity', component: 'moodle'},
+ {key: 'sessiontimeoutsoon', component: 'moodle'},
+ {key: 'extendsession', component: 'moodle'},
+ {key: 'cancel', component: 'moodle'}
+ ]).then(function(strings) {
+ Notification.confirm(
+ strings[0], // Title.
+ strings[1], // Message.
+ strings[2], // Extend session.
+ strings[3], // Cancel.
+ function() {
+ touchSession();
+ warningDisplayed = false;
+ // First wait is half the session timeout.
+ setTimeout(checkSession, checkFrequency * 5);
+ return true;
+ },
+ function() {
+ warningDisplayed = false;
+ setTimeout(checkSession, checkFrequency);
+ }
+ );
+ return true;
+ }).fail(Notification.exception);
+ } else {
+ setTimeout(checkSession, checkFrequency);
+ }
+ return true;
+ });
+ // We do not catch the fails from the above ajax call because they will fail when
+ // we are not logged in - we don't need to take any action then.
+ };
+
+ /**
+ * Start calling a function to check if the session is still alive.
+ */
+ var start = function() {
+ if (keepAliveFrequency > 0) {
+ setTimeout(touchSession, keepAliveFrequency);
+ } else {
+ // First wait is half the session timeout.
+ setTimeout(checkSession, checkFrequency * 5);
+ }
+ };
+
+ /**
+ * Don't allow more than one of these polling loops in a single page.
+ */
+ var init = function() {
+ // We only allow one concurrent instance of this checker.
+ if (started) {
+ return;
+ }
+ started = true;
+
+ start();
+ };
+
+ /**
+ * Start polling with more specific values for the frequency, timeout and message.
+ *
+ * @param {number} freq How ofter to poll the server.
+ * @param {number} timeout The time to wait for each request to the server.
+ * @param {string} message The message to display if the session is going to time out.
+ */
+ var keepalive = function(freq, timeout, message) {
+ // We only allow one concurrent instance of this checker.
+ if (started) {
+ return;
+ }
+ started = true;
+
+ keepAliveFrequency = freq * 1000;
+ keepAliveMessage = message;
+ requestTimeout = timeout * 1000;
+ start();
+ };
+
+ return {
+ keepalive: keepalive,
+ init: init
+ };
+});
diff --git a/lib/amd/src/page_global.js b/lib/amd/src/page_global.js
index 88360b8bf29..b92c97d8f20 100644
--- a/lib/amd/src/page_global.js
+++ b/lib/amd/src/page_global.js
@@ -26,11 +26,13 @@ define(
'jquery',
'core/custom_interaction_events',
'core/str',
+ 'core/network'
],
function(
$,
CustomEvents,
- Str
+ Str,
+ Network
) {
/**
@@ -127,6 +129,7 @@ function(
*/
var init = function() {
initActionOptionDropdownHandler();
+ Network.init();
};
return {
diff --git a/lib/classes/session/external.php b/lib/classes/session/external.php
new file mode 100644
index 00000000000..e6e0f0e70d6
--- /dev/null
+++ b/lib/classes/session/external.php
@@ -0,0 +1,95 @@
+.
+
+/**
+ * This class contains a list of webservice functions related to session.
+ *
+ * @package core
+ * @copyright 2019 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\session;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * This class contains a list of webservice functions related to session.
+ *
+ * @copyright 2019 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 2.9
+ */
+class external extends \external_api {
+
+ /**
+ * Returns description of touch_session() parameters.
+ *
+ * @return external_function_parameters
+ */
+ public static function touch_session_parameters() {
+ return new \external_function_parameters([]);
+ }
+
+ /**
+ * Extend the current session.
+ *
+ * @return array the mapping
+ */
+ public static function touch_session() {
+ \core\session\manager::touch_session(session_id());
+ return true;
+ }
+
+ /**
+ * Returns description of touch_session() result value.
+ *
+ * @return external_description
+ */
+ public static function touch_session_returns() {
+ return new \external_value(PARAM_BOOL, 'result');
+ }
+
+ /**
+ * Returns description of time_remaining() parameters.
+ *
+ * @return external_function_parameters
+ */
+ public static function time_remaining_parameters() {
+ return new \external_function_parameters([]);
+ }
+
+ /**
+ * Extend the current session.
+ *
+ * @return array the mapping
+ */
+ public static function time_remaining() {
+ return \core\session\manager::time_remaining(session_id());
+ }
+
+ /**
+ * Returns description of touch_session() result value.
+ *
+ * @return external_description
+ */
+ public static function time_remaining_returns() {
+ return new \external_single_structure(array (
+ 'userid' => new \external_value(PARAM_INTEGER, 'The current user id.'),
+ 'timeremaining' => new \external_value(PARAM_INTEGER, 'The number of seconds remaining in this session.')
+ ));
+ }
+}
diff --git a/lib/classes/session/manager.php b/lib/classes/session/manager.php
index 36083dd1dee..47c5df90125 100644
--- a/lib/classes/session/manager.php
+++ b/lib/classes/session/manager.php
@@ -364,6 +364,9 @@ class manager {
}
if ($timeout) {
+ if (defined('NO_SESSION_UPDATE') && NO_SESSION_UPDATE) {
+ return;
+ }
session_regenerate_id(true);
$_SESSION = array();
$DB->delete_records('sessions', array('id'=>$record->id));
@@ -398,7 +401,7 @@ class manager {
$updated = true;
}
- if ($updated) {
+ if ($updated && (!defined('NO_SESSION_UPDATE') || !NO_SESSION_UPDATE)) {
$update->id = $record->id;
$DB->update_record('sessions', $update);
}
@@ -632,6 +635,31 @@ class manager {
return self::$handler->session_exists($sid);
}
+ /**
+ * Return the number of seconds remaining in the current session.
+ * @param string $sid
+ */
+ public static function time_remaining($sid) {
+ global $DB, $CFG;
+
+ if (empty($CFG->version)) {
+ // Not installed yet, do not try to access database.
+ return ['userid' => 0, 'timeremaining' => $CFG->sessiontimeout];
+ }
+
+ // Note: add sessions->state checking here if it gets implemented.
+ if (!$record = $DB->get_record('sessions', array('sid' => $sid), 'id, userid, timemodified')) {
+ return ['userid' => 0, 'timeremaining' => $CFG->sessiontimeout];
+ }
+
+ if (empty($record->userid) or isguestuser($record->userid)) {
+ // Ignore guest and not-logged-in timeouts, there is very little risk here.
+ return ['userid' => 0, 'timeremaining' => $CFG->sessiontimeout];
+ } else {
+ return ['userid' => $record->userid, 'timeremaining' => $CFG->sessiontimeout - (time() - $record->timemodified)];
+ }
+ }
+
/**
* Fake last access for given session, this prevents session timeout.
* @param string $sid
@@ -955,9 +983,10 @@ class manager {
* @param string $identifier The string identifier for the message to show on failure.
* @param string $component The string component for the message to show on failure.
* @param int $frequency The update frequency in seconds.
+ * @param int $timeout The timeout of each request in seconds.
* @throws coding_exception IF the frequency is longer than the session lifetime.
*/
- public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null) {
+ public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null, $timeout = 0) {
global $CFG, $PAGE;
if ($frequency) {
@@ -966,19 +995,15 @@ class manager {
throw new \coding_exception('Keepalive frequency is longer than the session lifespan.');
}
} else {
- // A frequency of sessiontimeout / 3 allows for one missed request whilst still preserving the session.
- $frequency = $CFG->sessiontimeout / 3;
+ // A frequency of sessiontimeout / 10 matches the timeouts in core/network amd module.
+ $frequency = $CFG->sessiontimeout / 10;
}
- // Add the session keepalive script to the list of page output requirements.
- $sessionkeepaliveurl = new \moodle_url('/lib/sessionkeepalive_ajax.php');
- $PAGE->requires->string_for_js($identifier, $component);
- $PAGE->requires->yui_module('moodle-core-checknet', 'M.core.checknet.init', array(array(
- // The JS config takes this is milliseconds rather than seconds.
- 'frequency' => $frequency * 1000,
- 'message' => array($identifier, $component),
- 'uri' => $sessionkeepaliveurl->out(),
- )));
+ $PAGE->requires->js_call_amd('core/network', 'keepalive', array(
+ $frequency,
+ $timeout,
+ get_string($identifier, $component)
+ ));
}
/**
diff --git a/lib/db/services.php b/lib/db/services.php
index 708f2ccdb80..ce63f3d888b 100644
--- a/lib/db/services.php
+++ b/lib/db/services.php
@@ -677,6 +677,22 @@ $functions = array(
'loginrequired' => false,
'ajax' => true,
),
+ 'core_session_touch' => array(
+ 'classname' => 'core\session\external',
+ 'methodname' => 'touch_session',
+ 'description' => 'Keep the users session alive',
+ 'type' => 'read',
+ 'loginrequired' => true,
+ 'ajax' => true,
+ ),
+ 'core_session_time_remaining' => array(
+ 'classname' => 'core\session\external',
+ 'methodname' => 'time_remaining',
+ 'description' => 'Count the seconds remaining in this session',
+ 'type' => 'read',
+ 'loginrequired' => true,
+ 'ajax' => true,
+ ),
'core_files_get_files' => array(
'classname' => 'core_files_external',
'methodname' => 'get_files',
diff --git a/lib/outputrequirementslib.php b/lib/outputrequirementslib.php
index c7c0aa14070..f29905037e6 100644
--- a/lib/outputrequirementslib.php
+++ b/lib/outputrequirementslib.php
@@ -321,6 +321,7 @@ class page_requirements_manager {
$this->M_cfg = array(
'wwwroot' => $CFG->wwwroot,
'sesskey' => sesskey(),
+ 'sessiontimeout' => $CFG->sessiontimeout,
'themerev' => theme_get_revision(),
'slasharguments' => (int)(!empty($CFG->slasharguments)),
'theme' => $page->theme->name,
diff --git a/lib/sessionkeepalive_ajax.php b/lib/sessionkeepalive_ajax.php
deleted file mode 100644
index 65d1acfc1e9..00000000000
--- a/lib/sessionkeepalive_ajax.php
+++ /dev/null
@@ -1,36 +0,0 @@
-.
-
-/**
- * Ensure that session is kept alive.
- *
- * @copyright 2014 Andrew Nicols
- * @package core
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-define('AJAX_SCRIPT', true);
-require_once(__DIR__ . '/../config.php');
-
-// Require the session key - want to make sure that this isn't called
-// maliciously to keep a session alive longer than intended.
-if (!confirm_sesskey()) {
- header('HTTP/1.1 403 Forbidden');
- print_error('invalidsesskey');
-}
-
-// Update the session.
-\core\session\manager::touch_session(session_id());
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index 30dbd057adb..631f3e6b893 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -1,6 +1,9 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
+=== 3.8 ===
+* The yui checknet module is removed. Call \core\session\manager::keepalive instead.
+
=== 3.7 ===
* Nodes in the navigation api can have labels for each group. See set/get_collectionlabel().
* The method core_user::is_real_user() now returns false for userid = 0 parameter
diff --git a/lib/yui/build/moodle-core-checknet/assets/checknet.txt b/lib/yui/build/moodle-core-checknet/assets/checknet.txt
deleted file mode 100644
index d00491fd7e5..00000000000
--- a/lib/yui/build/moodle-core-checknet/assets/checknet.txt
+++ /dev/null
@@ -1 +0,0 @@
-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
deleted file mode 100644
index 3ee6b81c94c..00000000000
--- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js
+++ /dev/null
@@ -1,220 +0,0 @@
-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, {
- /**
- * Zero-based count of alerts displayed.
- *
- * @property _alertCount
- * @type Number
- * @private
- * @default 0
- */
- _alertCount: 0,
-
- /**
- * 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 the session key.
- sesskey: M.cfg.sesskey,
- // 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();
- }
- this._alertCount++;
- }
- }
- // If max alert not modified in args, check indefinitely.
- // Once max alert count iteration is reached, stop checking.
- if (this.get('maxalerts') === -1 || (this.get('maxalerts') - 1) >= this._alertCount) {
- // 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 4000
- */
- timeout: {
- value: 4000
- },
-
- /**
- * 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 10000
- */
- frequency: {
- value: 10000
- },
-
- /**
- * 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'
- ]
- },
-
- /**
- * Maxiumum count (not zero-based) of alerts to display for a single page load.
- *
- * @attribute maxalerts
- * @type Number
- * @value -1
- */
- maxalerts: {
- value: -1
- }
- }
-});
-
-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
deleted file mode 100644
index 0fe879b1162..00000000000
--- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js
+++ /dev/null
@@ -1 +0,0 @@
-YUI.add("moodle-core-checknet",function(e,t){function n(){n.superclass.constructor.apply(this,arguments)}e.extend(n,e.Base,{_alertCount:0,_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:{sesskey:M.cfg.sesskey,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._alertCount++)}(this.get("maxalerts")===-1||this.get("maxalerts")-1>=this._alertCount)&&this._scheduleCheck()}}})}},{NAME:"checkNet",ATTRS:{uri:{value:M.cfg.wwwroot+"/lib/yui/build/moodle-core-checknet/assets/checknet.txt"},timeout:{value:4e3},frequency:{value:1e4},message:{value:["networkdropped","moodle"]},maxalerts:{value:-1}}}),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
deleted file mode 100644
index 52d5b54d00a..00000000000
--- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js
+++ /dev/null
@@ -1,217 +0,0 @@
-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, {
- /**
- * Zero-based count of alerts displayed.
- *
- * @property _alertCount
- * @type Number
- * @private
- * @default 0
- */
- _alertCount: 0,
-
- /**
- * 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 the session key.
- sesskey: M.cfg.sesskey,
- // 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();
- }
- this._alertCount++;
- }
- }
- // If max alert not modified in args, check indefinitely.
- // Once max alert count iteration is reached, stop checking.
- if (this.get('maxalerts') === -1 || (this.get('maxalerts') - 1) >= this._alertCount) {
- // 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 4000
- */
- timeout: {
- value: 4000
- },
-
- /**
- * 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 10000
- */
- frequency: {
- value: 10000
- },
-
- /**
- * 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'
- ]
- },
-
- /**
- * Maxiumum count (not zero-based) of alerts to display for a single page load.
- *
- * @attribute maxalerts
- * @type Number
- * @value -1
- */
- maxalerts: {
- value: -1
- }
- }
-});
-
-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
deleted file mode 100644
index d00491fd7e5..00000000000
--- a/lib/yui/src/checknet/assets/checknet.txt
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/lib/yui/src/checknet/build.json b/lib/yui/src/checknet/build.json
deleted file mode 100644
index 406832d6ce1..00000000000
--- a/lib/yui/src/checknet/build.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "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
deleted file mode 100644
index 595620e3719..00000000000
--- a/lib/yui/src/checknet/js/checknet.js
+++ /dev/null
@@ -1,215 +0,0 @@
-// 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, {
- /**
- * Zero-based count of alerts displayed.
- *
- * @property _alertCount
- * @type Number
- * @private
- * @default 0
- */
- _alertCount: 0,
-
- /**
- * 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 the session key.
- sesskey: M.cfg.sesskey,
- // 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();
- }
- this._alertCount++;
- }
- }
- // If max alert not modified in args, check indefinitely.
- // Once max alert count iteration is reached, stop checking.
- if (this.get('maxalerts') === -1 || (this.get('maxalerts') - 1) >= this._alertCount) {
- // 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 4000
- */
- timeout: {
- value: 4000
- },
-
- /**
- * 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 10000
- */
- frequency: {
- value: 10000
- },
-
- /**
- * 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'
- ]
- },
-
- /**
- * Maxiumum count (not zero-based) of alerts to display for a single page load.
- *
- * @attribute maxalerts
- * @type Number
- * @value -1
- */
- maxalerts: {
- value: -1
- }
- }
-});
-
-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
deleted file mode 100644
index a63efb02920..00000000000
--- a/lib/yui/src/checknet/meta/checknet.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "moodle-core-checknet": {
- "requires": [
- "base-base",
- "moodle-core-notification-alert",
- "io-base"
- ]
- }
-}
diff --git a/mod/scorm/player.php b/mod/scorm/player.php
index 07bf5b5756e..f0cc78e674f 100644
--- a/mod/scorm/player.php
+++ b/mod/scorm/player.php
@@ -282,16 +282,9 @@ if (file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'.php'))
include_once($CFG->dirroot.'/mod/scorm/datamodels/scorm_12.php');
}
-// Add the checknet system to keep checking for a connection.
-$PAGE->requires->string_for_js('networkdropped', 'mod_scorm');
-// Build arguments to send to checknet JS.
-$args = array(
- 'message' => array('networkdropped', 'mod_scorm'),
- 'frequency' => 30000, // Frequency of network check.
- 'timeout' => 10000, // Timeout of network check.
- 'maxalerts' => 1 // Max number of alerts to be thrown.
-);
-$PAGE->requires->yui_module('moodle-core-checknet', 'M.core.checknet.init', array($args));
+// Add the keepalive system to keep checking for a connection.
+\core\session\manager::keepalive('networkdropped', 'mod_scorm', 30, 10);
+
echo $OUTPUT->footer();
// Set the start time of this SCO.
diff --git a/version.php b/version.php
index d62f6f0b3c3..acea8f1861c 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2019053000.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2019060600.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.