From 57996fe9565ab6703814e804105d3b178193e809 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 2 Dec 2014 11:21:25 +0800 Subject: [PATCH 1/2] MDL-44642 session: Add a client-side session keepalive system For pages where there will be significant user interaction in which the server is not updated, it may be necessary to poll the server periodically to touch the session. This makes use of the existing checknet script which was designed for checking network connectivity but fits into this use-case reasonably well. --- lib/classes/session/manager.php | 37 +++++++++++++++++++ lib/sessionkeepalive_ajax.php | 33 +++++++++++++++++ lib/sessionlib.php | 1 + .../moodle-core-checknet-debug.js | 2 + .../moodle-core-checknet-min.js | 2 +- .../moodle-core-checknet.js | 2 + lib/yui/src/checknet/js/checknet.js | 2 + 7 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 lib/sessionkeepalive_ajax.php diff --git a/lib/classes/session/manager.php b/lib/classes/session/manager.php index 0d461c28beb..8dfc9ceaf25 100644 --- a/lib/classes/session/manager.php +++ b/lib/classes/session/manager.php @@ -847,4 +847,41 @@ class manager { \core\session\manager::set_user($user); $event->trigger(); } + + /** + * Add a JS session keepalive to the page. + * + * A JS session keepalive script will be called to update the session modification time every $frequency seconds. + * + * Upon failure, the specified error message will be shown to the user. + * + * @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. + * @throws coding_exception IF the frequency is longer than the session lifetime. + */ + public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null) { + global $CFG, $PAGE; + + if ($frequency) { + if ($frequency > $CFG->sessiontimeout) { + // Sanity check the frequency. + 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; + } + + // 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(), + ))); + } + } diff --git a/lib/sessionkeepalive_ajax.php b/lib/sessionkeepalive_ajax.php new file mode 100644 index 00000000000..a2047a7c490 --- /dev/null +++ b/lib/sessionkeepalive_ajax.php @@ -0,0 +1,33 @@ +. + +/** + * 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(dirname(__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. +require_sesskey(); + +// Update the session. +\core\session\manager::touch_session(session_id()); diff --git a/lib/sessionlib.php b/lib/sessionlib.php index e60462f34d3..40640d688e2 100644 --- a/lib/sessionlib.php +++ b/lib/sessionlib.php @@ -82,6 +82,7 @@ function confirm_sesskey($sesskey=NULL) { */ function require_sesskey() { if (!confirm_sesskey()) { + header('HTTP/1.1 403 Forbidden'); print_error('invalidsesskey'); } } 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 index f5269612a24..aec63174fde 100644 --- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-debug.js @@ -79,6 +79,8 @@ Y.extend(CheckNet, Y.Base, { _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() }, 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 index f29d34998b3..0dc73e0c97f 100644 --- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet-min.js @@ -1 +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"]}); +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:{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._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 index f2e005e0369..a08ce4db27d 100644 --- a/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js +++ b/lib/yui/build/moodle-core-checknet/moodle-core-checknet.js @@ -79,6 +79,8 @@ Y.extend(CheckNet, Y.Base, { _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() }, diff --git a/lib/yui/src/checknet/js/checknet.js b/lib/yui/src/checknet/js/checknet.js index ee37fb5c57d..42e92db404c 100644 --- a/lib/yui/src/checknet/js/checknet.js +++ b/lib/yui/src/checknet/js/checknet.js @@ -77,6 +77,8 @@ Y.extend(CheckNet, Y.Base, { _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() }, From de1c6859ccccb06ae996f73a28166ab48a721129 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 2 Dec 2014 11:25:16 +0800 Subject: [PATCH 2/2] MDL-44642 grading: Add session keepalive during advanced grading setup --- grade/grading/form/guide/edit.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/grade/grading/form/guide/edit.php b/grade/grading/form/guide/edit.php index c4d481009e0..4d3b8313703 100644 --- a/grade/grading/form/guide/edit.php +++ b/grade/grading/form/guide/edit.php @@ -57,6 +57,10 @@ if ($mform->is_cancelled()) { redirect($returnurl); } +// 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();