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