Merge branch 'MDL-29201_20' of git://github.com/timhunt/moodle into MOODLE_20_STABLE
[moodle.git] / lib / sessionlib.php
blob73cbf2a1fb607f7b3cb84c5a56390207264a9ced
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * @package core
20 * @subpackage session
21 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
22 * @copyright 2008, 2009 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
28 /**
29 * Factory method returning moodle_session object.
30 * @return moodle_session
32 function session_get_instance() {
33 global $CFG, $DB;
35 static $session = null;
37 if (is_null($session)) {
38 if (empty($CFG->sessiontimeout)) {
39 $CFG->sessiontimeout = 7200;
42 if (defined('SESSION_CUSTOM_CLASS')) {
43 // this is a hook for webservices, key based login, etc.
44 if (defined('SESSION_CUSTOM_FILE')) {
45 require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
47 $session_class = SESSION_CUSTOM_CLASS;
48 $session = new $session_class();
50 } else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
51 // default recommended session type
52 $session = new database_session();
54 } else {
55 // legacy limited file based storage - some features and auth plugins will not work, sorry
56 $session = new legacy_file_session();
60 return $session;
63 /**
64 * Moodle session abstraction
66 * @package core
67 * @subpackage session
68 * @copyright 2008 Petr Skoda {@link http://skodak.org}
69 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
71 interface moodle_session {
72 /**
73 * Terminate current session
74 * @return void
76 public function terminate_current();
78 /**
79 * No more changes in session expected.
80 * Unblocks the sessions, other scripts may start executing in parallel.
81 * @return void
83 public function write_close();
85 /**
86 * Check for existing session with id $sid
87 * @param unknown_type $sid
88 * @return boolean true if session found.
90 public function session_exists($sid);
93 /**
94 * Class handling all session and cookies related stuff.
96 * @package core
97 * @subpackage session
98 * @copyright 2009 Petr Skoda {@link http://skodak.org}
99 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
101 abstract class session_stub implements moodle_session {
102 protected $justloggedout;
104 public function __construct() {
105 global $CFG;
107 if (NO_MOODLE_COOKIES) {
108 // session not used at all
109 $CFG->usesid = false;
111 $_SESSION = array();
112 $_SESSION['SESSION'] = new stdClass();
113 $_SESSION['USER'] = new stdClass();
115 } else {
116 $this->prepare_cookies();
117 $this->init_session_storage();
119 $newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
121 // cookieless mode is prevented for security reasons
122 $CFG->usesid = false;
123 ini_set('session.use_trans_sid', '0');
125 session_name('MoodleSession'.$CFG->sessioncookie);
126 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
127 session_start();
128 if (!isset($_SESSION['SESSION'])) {
129 $_SESSION['SESSION'] = new stdClass();
130 if (!$newsession and !$this->justloggedout) {
131 $_SESSION['SESSION']->has_timed_out = true;
134 if (!isset($_SESSION['USER'])) {
135 $_SESSION['USER'] = new stdClass();
139 $this->check_user_initialised();
141 $this->check_security();
145 * Terminates active moodle session
147 public function terminate_current() {
148 global $CFG, $SESSION, $USER, $DB;
150 try {
151 $DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED));
152 } catch (Exception $ignored) {
153 // probably install/upgrade - ignore this problem
156 if (NO_MOODLE_COOKIES) {
157 return;
160 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
161 $_SESSION = array();
162 $_SESSION['SESSION'] = new stdClass();
163 $_SESSION['USER'] = new stdClass();
164 $_SESSION['USER']->id = 0;
165 if (isset($CFG->mnet_localhost_id)) {
166 $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
168 $SESSION = $_SESSION['SESSION']; // this may not work properly
169 $USER = $_SESSION['USER']; // this may not work properly
171 $file = null;
172 $line = null;
173 if (headers_sent($file, $line)) {
174 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
177 // now let's try to get a new session id and delete the old one
178 $this->justloggedout = true;
179 session_regenerate_id(true);
180 $this->justloggedout = false;
182 // write the new session
183 session_write_close();
187 * No more changes in session expected.
188 * Unblocks the sessions, other scripts may start executing in parallel.
189 * @return void
191 public function write_close() {
192 if (NO_MOODLE_COOKIES) {
193 return;
196 session_write_close();
200 * Initialise $USER object, handles google access
201 * and sets up not logged in user properly.
203 * @return void
205 protected function check_user_initialised() {
206 global $CFG;
208 if (isset($_SESSION['USER']->id)) {
209 // already set up $USER
210 return;
213 $user = null;
215 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
216 if (is_web_crawler()) {
217 $user = guest_user();
219 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
220 // automaticaly log in users coming from search engine results
221 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
222 $user = guest_user();
223 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
224 $user = guest_user();
229 if (!$user) {
230 $user = new stdClass();
231 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
232 if (isset($CFG->mnet_localhost_id)) {
233 $user->mnethostid = $CFG->mnet_localhost_id;
234 } else {
235 $user->mnethostid = 1;
238 session_set_user($user);
242 * Does various session security checks
243 * @global void
245 protected function check_security() {
246 global $CFG;
248 if (NO_MOODLE_COOKIES) {
249 return;
252 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
253 /// Make sure current IP matches the one for this session
254 $remoteaddr = getremoteaddr();
256 if (empty($_SESSION['USER']->sessionip)) {
257 $_SESSION['USER']->sessionip = $remoteaddr;
260 if ($_SESSION['USER']->sessionip != $remoteaddr) {
261 // this is a security feature - terminate the session in case of any doubt
262 $this->terminate_current();
263 print_error('sessionipnomatch2', 'error');
269 * Prepare cookies and various system settings
271 protected function prepare_cookies() {
272 global $CFG;
274 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
275 $CFG->cookiesecure = 0;
278 if (!isset($CFG->cookiehttponly)) {
279 $CFG->cookiehttponly = 0;
282 /// Set sessioncookie and sessioncookiepath variable if it isn't already
283 if (!isset($CFG->sessioncookie)) {
284 $CFG->sessioncookie = '';
286 if (!isset($CFG->sessioncookiedomain)) {
287 $CFG->sessioncookiedomain = '';
289 if (!isset($CFG->sessioncookiepath)) {
290 $CFG->sessioncookiepath = '/';
293 //discard session ID from POST, GET and globals to tighten security,
294 //this session fixation prevention can not be used in cookieless mode
295 if (empty($CFG->usesid)) {
296 unset(${'MoodleSession'.$CFG->sessioncookie});
297 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
298 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
299 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
301 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
302 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
303 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
308 * Inits session storage.
310 protected abstract function init_session_storage();
314 * Legacy moodle sessions stored in files, not recommended any more.
316 * @package core
317 * @subpackage session
318 * @copyright 2009 Petr Skoda {@link http://skodak.org}
319 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
321 class legacy_file_session extends session_stub {
322 protected function init_session_storage() {
323 global $CFG;
325 ini_set('session.save_handler', 'files');
327 // Some distros disable GC by setting probability to 0
328 // overriding the PHP default of 1
329 // (gc_probability is divided by gc_divisor, which defaults to 1000)
330 if (ini_get('session.gc_probability') == 0) {
331 ini_set('session.gc_probability', 1);
334 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
336 // make sure sessions dir exists and is writable, throws exception if not
337 make_upload_directory('sessions');
339 // Need to disable debugging since disk_free_space()
340 // will fail on very large partitions (see MDL-19222)
341 $freespace = @disk_free_space($CFG->dataroot.'/sessions');
342 if (!($freespace > 2048) and $freespace !== false) {
343 print_error('sessiondiskfull', 'error');
345 ini_set('session.save_path', $CFG->dataroot .'/sessions');
348 * Check for existing session with id $sid
349 * @param unknown_type $sid
350 * @return boolean true if session found.
352 public function session_exists($sid){
353 global $CFG;
355 $sid = clean_param($sid, PARAM_FILE);
356 $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
357 return file_exists($sessionfile);
362 * Recommended moodle session storage.
364 * @package core
365 * @subpackage session
366 * @copyright 2009 Petr Skoda {@link http://skodak.org}
367 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
369 class database_session extends session_stub {
370 protected $record = null;
371 protected $database = null;
373 public function __construct() {
374 global $DB;
375 $this->database = $DB;
376 parent::__construct();
378 if (!empty($this->record->state)) {
379 // something is very wrong
380 session_kill($this->record->sid);
382 if ($this->record->state == 9) {
383 print_error('dbsessionmysqlpacketsize', 'error');
388 public function session_exists($sid){
389 global $CFG;
390 try {
391 $sql = "SELECT * FROM {sessions} WHERE timemodified < ? AND sid=? AND state=?";
392 $params = array(time() + $CFG->sessiontimeout, $sid, 0);
394 return $this->database->record_exists_sql($sql, $params);
395 } catch (dml_exception $ex) {
396 error_log('Error checking existance of database session');
397 return false;
401 protected function init_session_storage() {
402 global $CFG;
404 // gc only from CRON - individual user timeouts now checked during each access
405 ini_set('session.gc_probability', 0);
407 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
409 $result = session_set_save_handler(array($this, 'handler_open'),
410 array($this, 'handler_close'),
411 array($this, 'handler_read'),
412 array($this, 'handler_write'),
413 array($this, 'handler_destroy'),
414 array($this, 'handler_gc'));
415 if (!$result) {
416 print_error('dbsessionhandlerproblem', 'error');
420 public function handler_open($save_path, $session_name) {
421 return true;
424 public function handler_close() {
425 if (isset($this->record->id)) {
426 $this->database->release_session_lock($this->record->id);
428 $this->record = null;
429 return true;
432 public function handler_read($sid) {
433 global $CFG;
435 if ($this->record and $this->record->sid != $sid) {
436 error_log('Weird error reading database session - mismatched sid');
437 return '';
440 try {
441 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
442 $this->database->get_session_lock($record->id);
444 } else {
445 $record = new stdClass();
446 $record->state = 0;
447 $record->sid = $sid;
448 $record->sessdata = null;
449 $record->userid = 0;
450 $record->timecreated = $record->timemodified = time();
451 $record->firstip = $record->lastip = getremoteaddr();
452 $record->id = $this->database->insert_record_raw('sessions', $record);
454 $this->database->get_session_lock($record->id);
456 } catch (dml_exception $ex) {
457 error_log('Can not read or insert database sessions');
458 return '';
461 // verify timeout
462 if ($record->timemodified + $CFG->sessiontimeout < time()) {
463 $ignoretimeout = false;
464 if (!empty($record->userid)) { // skips not logged in
465 if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
466 if (!isguestuser($user)) {
467 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
468 foreach($authsequence as $authname) {
469 $authplugin = get_auth_plugin($authname);
470 if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
471 $ignoretimeout = true;
472 break;
478 if ($ignoretimeout) {
479 //refresh session
480 $record->timemodified = time();
481 try {
482 $this->database->update_record('sessions', $record);
483 } catch (dml_exception $ex) {
484 error_log('Can not refresh database session');
485 return '';
487 } else {
488 //time out session
489 $record->state = 0;
490 $record->sessdata = null;
491 $record->userid = 0;
492 $record->timecreated = $record->timemodified = time();
493 $record->firstip = $record->lastip = getremoteaddr();
494 try {
495 $this->database->update_record('sessions', $record);
496 } catch (dml_exception $ex) {
497 error_log('Can not time out database session');
498 return '';
503 $data = is_null($record->sessdata) ? '' : base64_decode($record->sessdata);
505 unset($record->sessdata); // conserve memory
506 $this->record = $record;
508 return $data;
511 public function handler_write($sid, $session_data) {
512 global $USER;
514 // TODO: MDL-20625 we need to rollback all active transactions and log error if any open needed
516 $userid = 0;
517 if (!empty($USER->realuser)) {
518 $userid = $USER->realuser;
519 } else if (!empty($USER->id)) {
520 $userid = $USER->id;
523 if (isset($this->record->id)) {
524 $record = new stdClass();
525 $record->state = 0;
526 $record->sid = $sid; // might be regenerating sid
527 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
528 $this->record->userid = $userid;
529 $this->record->timemodified = time();
530 $this->record->lastip = getremoteaddr();
532 // TODO: verify session changed before doing update,
533 // also make sure the timemodified field is changed only every 10s if nothing else changes MDL-20462
535 try {
536 $this->database->update_record_raw('sessions', $this->record);
537 } catch (dml_exception $ex) {
538 if ($this->database->get_dbfamily() === 'mysql') {
539 try {
540 $this->database->set_field('sessions', 'state', 9, array('id'=>$this->record->id));
541 } catch (Exception $ignored) {
544 error_log('Can not write database session - please verify max_allowed_packet is at least 4M!');
545 } else {
546 error_log('Can not write database session');
550 } else {
551 // session already destroyed
552 $record = new stdClass();
553 $record->state = 0;
554 $record->sid = $sid;
555 $record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
556 $record->userid = $userid;
557 $record->timecreated = $record->timemodified = time();
558 $record->firstip = $record->lastip = getremoteaddr();
559 $record->id = $this->database->insert_record_raw('sessions', $record);
560 $this->record = $record;
562 try {
563 $this->database->get_session_lock($this->record->id);
564 } catch (dml_exception $ex) {
565 error_log('Can not write new database session');
569 return true;
572 public function handler_destroy($sid) {
573 session_kill($sid);
575 if (isset($this->record->id) and $this->record->sid === $sid) {
576 $this->database->release_session_lock($this->record->id);
577 $this->record = null;
580 return true;
583 public function handler_gc($ignored_maxlifetime) {
584 session_gc();
585 return true;
590 * returns true if legacy session used.
591 * @return bool true if legacy(==file) based session used
593 function session_is_legacy() {
594 global $CFG, $DB;
595 return ((isset($CFG->dbsessions) and !$CFG->dbsessions) or !$DB->session_lock_supported());
599 * Terminates all sessions, auth hooks are not executed.
600 * Useful in upgrade scripts.
602 function session_kill_all() {
603 global $CFG, $DB;
605 // always check db table - custom session classes use sessions table
606 try {
607 $DB->delete_records('sessions');
608 } catch (dml_exception $ignored) {
609 // do not show any warnings - might be during upgrade/installation
612 if (session_is_legacy()) {
613 $sessiondir = "$CFG->dataroot/sessions";
614 if (is_dir($sessiondir)) {
615 foreach (glob("$sessiondir/sess_*") as $filename) {
616 @unlink($filename);
623 * Mark session as accessed, prevents timeouts.
624 * @param string $sid
626 function session_touch($sid) {
627 global $CFG, $DB;
629 // always check db table - custom session classes use sessions table
630 try {
631 $sql = "UPDATE {sessions} SET timemodified=? WHERE sid=?";
632 $params = array(time(), $sid);
633 $DB->execute($sql, $params);
634 } catch (dml_exception $ignored) {
635 // do not show any warnings - might be during upgrade/installation
638 if (session_is_legacy()) {
639 $sid = clean_param($sid, PARAM_FILE);
640 $sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
641 if (file_exists($sessionfile)) {
642 // if the file is locked it means that it will be updated anyway
643 @touch($sessionfile);
649 * Terminates one sessions, auth hooks are not executed.
651 * @param string $sid session id
653 function session_kill($sid) {
654 global $CFG, $DB;
656 // always check db table - custom session classes use sessions table
657 try {
658 $DB->delete_records('sessions', array('sid'=>$sid));
659 } catch (dml_exception $ignored) {
660 // do not show any warnings - might be during upgrade/installation
663 if (session_is_legacy()) {
664 $sid = clean_param($sid, PARAM_FILE);
665 $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
666 if (file_exists($sessionfile)) {
667 @unlink($sessionfile);
673 * Terminates all sessions of one user, auth hooks are not executed.
674 * NOTE: This can not work for file based sessions!
676 * @param int $userid user id
678 function session_kill_user($userid) {
679 global $CFG, $DB;
681 // always check db table - custom session classes use sessions table
682 try {
683 $DB->delete_records('sessions', array('userid'=>$userid));
684 } catch (dml_exception $ignored) {
685 // do not show any warnings - might be during upgrade/installation
688 if (session_is_legacy()) {
689 // log error?
694 * Session garbage collection
695 * - verify timeout for all users
696 * - kill sessions of all deleted users
697 * - kill sessions of users with disabled plugins or 'nologin' plugin
699 * NOTE: this can not work when legacy file sessions used!
701 function session_gc() {
702 global $CFG, $DB;
704 $maxlifetime = $CFG->sessiontimeout;
706 try {
707 /// kill all sessions of deleted users
708 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
710 /// kill sessions of users with disabled plugins
711 $auth_sequence = get_enabled_auth_plugins(true);
712 $auth_sequence = array_flip($auth_sequence);
713 unset($auth_sequence['nologin']); // no login allowed
714 $auth_sequence = array_flip($auth_sequence);
715 $notplugins = null;
716 list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
717 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
719 /// now get a list of time-out candidates
720 $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
721 FROM {user} u
722 JOIN {sessions} s ON s.userid = u.id
723 WHERE s.timemodified + ? < ? AND u.id <> ?";
724 $params = array($maxlifetime, time(), $CFG->siteguest);
726 $authplugins = array();
727 foreach($auth_sequence as $authname) {
728 $authplugins[$authname] = get_auth_plugin($authname);
730 $rs = $DB->get_recordset_sql($sql, $params);
731 foreach ($rs as $user) {
732 foreach ($authplugins as $authplugin) {
733 if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
734 continue;
737 $DB->delete_records('sessions', array('sid'=>$user->sid));
739 $rs->close();
741 $purgebefore = time() - $maxlifetime;
742 // delete expired sessions for guest user account
743 $DB->delete_records_select('sessions', 'userid = ? AND timemodified < ?', array($CFG->siteguest, $purgebefore));
744 // delete expired sessions for userid = 0 (not logged in)
745 $DB->delete_records_select('sessions', 'userid = 0 AND timemodified < ?', array($purgebefore));
746 } catch (dml_exception $ex) {
747 error_log('Error gc-ing sessions');
752 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
753 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
754 * sesskey string if $USER exists, or boolean false if not.
756 * @uses $USER
757 * @return string
759 function sesskey() {
760 // note: do not use $USER because it may not be initialised yet
761 if (empty($_SESSION['USER']->sesskey)) {
762 $_SESSION['USER']->sesskey = random_string(10);
765 return $_SESSION['USER']->sesskey;
770 * Check the sesskey and return true of false for whether it is valid.
771 * (You might like to imagine this function is called sesskey_is_valid().)
773 * Every script that lets the user perform a significant action (that is,
774 * changes data in the database) should check the sesskey before doing the action.
775 * Depending on your code flow, you may want to use the {@link require_sesskey()}
776 * helper function.
778 * @param string $sesskey The sesskey value to check (optional). Normally leave this blank
779 * and this function will do required_param('sesskey', ...).
780 * @return bool whether the sesskey sent in the request matches the one stored in the session.
782 function confirm_sesskey($sesskey=NULL) {
783 global $USER;
785 if (!empty($USER->ignoresesskey)) {
786 return true;
789 if (empty($sesskey)) {
790 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
793 return (sesskey() === $sesskey);
797 * Check the session key using {@link confirm_sesskey()},
798 * and cause a fatal error if it does not match.
800 function require_sesskey() {
801 if (!confirm_sesskey()) {
802 print_error('invalidsesskey');
807 * Sets a moodle cookie with a weakly encrypted username
809 * @param string $username to encrypt and place in a cookie, '' means delete current cookie
810 * @return void
812 function set_moodle_cookie($username) {
813 global $CFG;
815 if (NO_MOODLE_COOKIES) {
816 return;
819 if ($username === 'guest') {
820 // keep previous cookie in case of guest account login
821 return;
824 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
826 // delete old cookie
827 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
829 if ($username !== '') {
830 // set username cookie for 60 days
831 setcookie($cookiename, rc4encrypt($username), time()+(DAYSECS*60), $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
836 * Gets a moodle cookie with a weakly encrypted username
838 * @return string username
840 function get_moodle_cookie() {
841 global $CFG;
843 if (NO_MOODLE_COOKIES) {
844 return '';
847 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
849 if (empty($_COOKIE[$cookiename])) {
850 return '';
851 } else {
852 $username = rc4decrypt($_COOKIE[$cookiename]);
853 if ($username === 'guest' or $username === 'nobody') {
854 // backwards compatibility - we do not set these cookies any more
855 return '';
857 return $username;
863 * Setup $USER object - called during login, loginas, etc.
864 * Preloads capabilities and checks enrolment plugins
866 * @param stdClass $user full user record object
867 * @return void
869 function session_set_user($user) {
870 $_SESSION['USER'] = $user;
871 unset($_SESSION['USER']->description); // conserve memory
872 if (!isset($_SESSION['USER']->access)) {
873 // check enrolments and load caps only once
874 enrol_check_plugins($_SESSION['USER']);
875 load_all_capabilities();
877 sesskey(); // init session key
881 * Is current $USER logged-in-as somebody else?
882 * @return bool
884 function session_is_loggedinas() {
885 return !empty($_SESSION['USER']->realuser);
889 * Returns the $USER object ignoring current login-as session
890 * @return stdClass user object
892 function session_get_realuser() {
893 if (session_is_loggedinas()) {
894 return $_SESSION['REALUSER'];
895 } else {
896 return $_SESSION['USER'];
901 * Login as another user - no security checks here.
902 * @param int $userid
903 * @param stdClass $context
904 * @return void
906 function session_loginas($userid, $context) {
907 if (session_is_loggedinas()) {
908 return;
911 // switch to fresh new $SESSION
912 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
913 $_SESSION['SESSION'] = new stdClass();
915 /// Create the new $USER object with all details and reload needed capabilities
916 $_SESSION['REALUSER'] = $_SESSION['USER'];
917 $user = get_complete_user_data('id', $userid);
918 $user->realuser = $_SESSION['REALUSER']->id;
919 $user->loginascontext = $context;
920 session_set_user($user);
924 * Sets up current user and course environment (lang, etc.) in cron.
925 * Do not use outside of cron script!
927 * @param stdClass $user full user object, null means default cron user (admin)
928 * @param $course full course record, null means $SITE
929 * @return void
931 function cron_setup_user($user = NULL, $course = NULL) {
932 global $CFG, $SITE, $PAGE;
934 static $cronuser = NULL;
935 static $cronsession = NULL;
937 if (empty($cronuser)) {
938 /// ignore admins timezone, language and locale - use site default instead!
939 $cronuser = get_admin();
940 $cronuser->timezone = $CFG->timezone;
941 $cronuser->lang = '';
942 $cronuser->theme = '';
943 unset($cronuser->description);
945 $cronsession = new stdClass();
948 if (!$user) {
949 // cached default cron user (==modified admin for now)
950 session_set_user($cronuser);
951 $_SESSION['SESSION'] = $cronsession;
953 } else {
954 // emulate real user session - needed for caps in cron
955 if ($_SESSION['USER']->id != $user->id) {
956 session_set_user($user);
957 $_SESSION['SESSION'] = new stdClass();
961 // TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
962 // Temporary hack so that cron does not give fatal errors.
963 $PAGE = new moodle_page();
964 if ($course) {
965 $PAGE->set_course($course);
966 } else {
967 $PAGE->set_course($SITE);
970 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
975 * Enable cookieless sessions by including $CFG->usesid=true;
976 * in config.php.
977 * Based on code from php manual by Richard at postamble.co.uk
978 * Attempts to use cookies if cookies not present then uses session ids attached to all urls and forms to pass session id from page to page.
979 * If site is open to google, google is given guest access as usual and there are no sessions. No session ids will be attached to urls for googlebot.
980 * This doesn't require trans_sid to be turned on but this is recommended for better performance
981 * you should put :
982 * session.use_trans_sid = 1
983 * in your php.ini file and make sure that you don't have a line like this in your php.ini
984 * session.use_only_cookies = 1
985 * @author Richard at postamble.co.uk and Jamie Pratt
986 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
989 * You won't call this function directly. This function is used to process
990 * text buffered by php in an output buffer. All output is run through this function
991 * before it is ouput.
992 * @param string $buffer is the output sent from php
993 * @return string the output sent to the browser
995 function sid_ob_rewrite($buffer){
996 $replacements = array(
997 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
998 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
1000 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
1001 $buffer = preg_replace('/<form\s[^>]*>/i',
1002 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
1004 return $buffer;
1007 * You won't call this function directly. This function is used to process
1008 * text buffered by php in an output buffer. All output is run through this function
1009 * before it is ouput.
1010 * This function only processes absolute urls, it is used when we decide that
1011 * php is processing other urls itself but needs some help with internal absolute urls still.
1012 * @param string $buffer is the output sent from php
1013 * @return string the output sent to the browser
1015 function sid_ob_rewrite_absolute($buffer){
1016 $replacements = array(
1017 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
1018 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
1020 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
1021 $buffer = preg_replace('/<form\s[^>]*>/i',
1022 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
1023 return $buffer;
1027 * A function to process link, a and script tags found
1028 * by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
1030 function sid_rewrite_link_tag($matches){
1031 $url = $matches[4];
1032 $url = sid_process_url($url);
1033 return $matches[1].$url.$matches[5];
1037 * You can call this function directly. This function is used to process
1038 * urls to add a moodle session id to the url for internal links.
1039 * @param string $url is a url
1040 * @return string the processed url
1042 function sid_process_url($url) {
1043 global $CFG;
1045 if ((preg_match('/^(http|https):/i', $url)) // absolute url
1046 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
1047 return $url; //don't attach sessid to non local urls
1049 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
1050 return $url; //don't attach sessid to anchors
1052 if (strpos($url, session_name())!==FALSE) {
1053 return $url; //don't attach sessid to url that already has one sessid
1055 if (strpos($url, "?")===FALSE) {
1056 $append = "?".strip_tags(session_name() . '=' . session_id());
1057 } else {
1058 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
1060 //put sessid before any anchor
1061 $p = strpos($url, "#");
1062 if ($p!==FALSE){
1063 $anch = substr($url, $p);
1064 $url = substr($url, 0, $p).$append.$anch ;
1065 } else {
1066 $url .= $append ;
1068 return $url;
1072 * Call this function before there has been any output to the browser to
1073 * buffer output and add session ids to all internal links.
1075 function sid_start_ob(){
1076 global $CFG;
1077 //don't attach sess id for bots
1079 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
1080 if (!empty($CFG->opentogoogle)) {
1081 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
1082 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1083 $CFG->usesid=false;
1084 return;
1086 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
1087 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1088 $CFG->usesid=false;
1089 return;
1092 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
1093 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1094 $CFG->usesid=false;
1095 return;
1099 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
1101 if (ini_get('session.use_trans_sid') != 0) {
1102 // use trans sid as its available
1103 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
1104 ob_start('sid_ob_rewrite_absolute');
1105 } else {
1106 //rewrite all links ourselves
1107 ob_start('sid_ob_rewrite');