MDL-14992 pg session locking (8.2 and later only), refactoring and db session not...
[moodle.git] / lib / sessionlib.php
CommitLineData
57f7b7ce 1<?php //$Id$
2
542797b4 3/**
4 * Factory method returning moodle_session object.
5 * @return moodle_session
6 */
b7b64ff2 7function session_get_instance() {
5e9dd017 8 global $CFG, $DB;
0a2092a3 9
0ad6b20c 10 static $session = null;
11
12 if (is_null($session)) {
0a2092a3 13 if (defined('SESSION_CUSTOM')) {
14 // this is a hook for custom session handling, webservices, etc.
15 if (defined('SESSION_CUSTOM_FILE')) {
16 require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
17 }
18 $session_class = SESSION_CUSTOM;
19 $session = new $session_class();
20
5e9dd017 21 //} else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
22 } else if (!empty($CFG->dbsessions) and $DB->session_lock_supported()) {
0a2092a3 23 // default recommended session type
24 $session = new database_session();
25
26 } else {
27 // legacy limited file based storage - some features and auth plugins will not work, sorry
28 $session = new legacy_file_session();
29 }
0ad6b20c 30 }
31
32 return $session;
33}
34
0a2092a3 35interface moodle_session {
36 public function terminate();
37}
38
57f7b7ce 39/**
40 * Class handling all session and cookies related stuff.
41 */
0a2092a3 42abstract class session_stub implements moodle_session {
b7b64ff2 43 public function __construct() {
57f7b7ce 44 global $CFG;
57f7b7ce 45
0a2092a3 46 if (!defined('NO_MOODLE_COOKIES')) {
47 if (CLI_SCRIPT) {
48 // CLI scripts can not have session
49 define('NO_MOODLE_COOKIES', true);
50 } else {
51 define('NO_MOODLE_COOKIES', false);
52 }
57f7b7ce 53 }
54
0ad6b20c 55 if (NO_MOODLE_COOKIES) {
0a2092a3 56 // session not used at all
57 $CFG->usesid = false;
58
0ad6b20c 59 $_SESSION = array();
60 $_SESSION['SESSION'] = new object();
0a2092a3 61 $_SESSION['USER'] = new object();
0ad6b20c 62
63 } else {
0a2092a3 64 $this->prepare_cookies();
65 $this->init_session_storage();
66
67 if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
68 sid_start_ob();
69 }
70
57f7b7ce 71 session_name('MoodleSession'.$CFG->sessioncookie);
e6e13284 72 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
57f7b7ce 73 @session_start();
74 if (!isset($_SESSION['SESSION'])) {
75 $_SESSION['SESSION'] = new object();
57f7b7ce 76 }
77 if (!isset($_SESSION['USER'])) {
78 $_SESSION['USER'] = new object();
79 }
57f7b7ce 80 }
57f7b7ce 81
b7b64ff2 82 $this->check_user_initialised();
9bda43e6 83
84 $this->check_security();
b7b64ff2 85 }
86
87 /**
88 * Initialise $USER object, handles google access.
89 *
90 * @return void
91 */
92 protected function check_user_initialised() {
93 if (isset($_SESSION['USER']->id)) {
94 // already set up $USER
95 return;
96 }
97
98 $user = null;
99
100 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
101 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
102 // allow web spiders in as guest users
103 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
104 $user = guest_user();
105 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
106 $user = guest_user();
107 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) { // Yahoo
108 $user = guest_user();
109 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) { // Zoomspider
110 $user = guest_user();
111 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) { // MSN Search
112 $user = guest_user();
113 }
114 }
1b813f5c 115 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
b7b64ff2 116 // automaticaly log in users coming from search engine results
117 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
118 $user = guest_user();
119 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
120 $user = guest_user();
121 }
122 }
123 }
124
125 if (!$user) {
126 $user = new object();
127 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
0ad6b20c 128 if (isset($CFG->mnet_localhost_id)) {
b7b64ff2 129 $user->mnethostid = $CFG->mnet_localhost_id;
0a2092a3 130 } else {
131 $user->mnethostid = 1;
57f7b7ce 132 }
133 }
b7b64ff2 134 session_set_user($user);
57f7b7ce 135 }
136
9bda43e6 137 /**
138 * Does various session security checks
139 * @global void
140 */
93f66983 141 protected function check_security() {
142 global $CFG;
143
0a2092a3 144 if (NO_MOODLE_COOKIES) {
145 return;
146 }
147
9bda43e6 148 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
149 /// Make sure current IP matches the one for this session
93f66983 150 $remoteaddr = getremoteaddr();
151
152 if (empty($_SESSION['USER']->sessionip)) {
153 $_SESSION['USER']->sessionip = $remoteaddr;
154 }
155
156 if ($_SESSION['USER']->sessionip != $remoteaddr) {
9bda43e6 157 // this is a security feature - terminate the session in case of any doubt
158 $this->terminate();
159 print_error('sessionipnomatch2', 'error');
93f66983 160 }
161 }
93f66983 162 }
163
57f7b7ce 164 /**
165 * Terminates active moodle session
166 */
167 public function terminate() {
168 global $CFG, $SESSION, $USER;
169
0ad6b20c 170 $_SESSION = array();
57f7b7ce 171
0ad6b20c 172 $SESSION = new object();
57f7b7ce 173 $USER = new object();
174 $USER->id = 0;
175 if (isset($CFG->mnet_localhost_id)) {
176 $USER->mnethostid = $CFG->mnet_localhost_id;
177 }
178
0ad6b20c 179 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
180 $file = null;
181 $line = null;
182 if (headers_sent($file, $line)) {
183 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
57f7b7ce 184 }
57f7b7ce 185
9bda43e6 186 // now let's try to get a new session id and destroy the old one
187 @session_regenerate_id(true);
188
189 // close the session
0ad6b20c 190 @session_write_close();
57f7b7ce 191 }
192
193 /**
194 * Prepare cookies and varions system settings
195 */
b7b64ff2 196 protected function prepare_cookies() {
0a2092a3 197 global $CFG;
57f7b7ce 198
11e7b506 199 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
57f7b7ce 200 $CFG->cookiesecure = 0;
201 }
202
203 if (!isset($CFG->cookiehttponly)) {
204 $CFG->cookiehttponly = 0;
205 }
206
207 /// Set sessioncookie and sessioncookiepath variable if it isn't already
208 if (!isset($CFG->sessioncookie)) {
209 $CFG->sessioncookie = '';
210 }
e6e13284 211 if (!isset($CFG->sessioncookiedomain)) {
212 $CFG->sessioncookiedomain = '';
213 }
57f7b7ce 214 if (!isset($CFG->sessioncookiepath)) {
215 $CFG->sessioncookiepath = '/';
216 }
217
218 //discard session ID from POST, GET and globals to tighten security,
219 //this session fixation prevention can not be used in cookieless mode
220 if (empty($CFG->usesid)) {
221 unset(${'MoodleSession'.$CFG->sessioncookie});
222 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
223 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
b7b64ff2 224 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
57f7b7ce 225 }
226 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
227 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
228 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
229 }
57f7b7ce 230 }
231
232 /**
233 * Inits session storage.
234 */
f61a032a 235 protected abstract function init_session_storage();
236
237}
238
239/**
240 * Legacy moodle sessions stored in files, not recommended any more.
241 */
0a2092a3 242class legacy_file_session extends session_stub {
b7b64ff2 243 protected function init_session_storage() {
57f7b7ce 244 global $CFG;
245
0a2092a3 246 ini_set('session.save_handler', 'files');
247
f61a032a 248 // Some distros disable GC by setting probability to 0
249 // overriding the PHP default of 1
250 // (gc_probability is divided by gc_divisor, which defaults to 1000)
251 if (ini_get('session.gc_probability') == 0) {
252 ini_set('session.gc_probability', 1);
253 }
57f7b7ce 254
3b1a9849 255 if (empty($CFG->sessiontimeout)) {
256 $CFG->sessiontimeout = 7200;
f61a032a 257 }
3b1a9849 258 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
57f7b7ce 259
f61a032a 260 if (!file_exists($CFG->dataroot .'/sessions')) {
261 make_upload_directory('sessions');
262 }
263 if (!is_writable($CFG->dataroot .'/sessions/')) {
264 print_error('sessionnotwritable', 'error');
57f7b7ce 265 }
f61a032a 266 ini_set('session.save_path', $CFG->dataroot .'/sessions');
267 }
268}
269
270/**
271 * Recommended moodle session storage.
272 */
0a2092a3 273class database_session extends session_stub {
274 protected $record = null;
275 protected $database = null;
276
f61a032a 277 protected function init_session_storage() {
278 global $CFG;
279
0a2092a3 280 if (ini_get('session.gc_probability') == 0) {
281 ini_set('session.gc_probability', 1);
282 }
283
284 if (!empty($CFG->sessiontimeout)) {
285 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
286 }
287
288 $result = session_set_save_handler(array($this, 'handler_open'),
289 array($this, 'handler_close'),
290 array($this, 'handler_read'),
291 array($this, 'handler_write'),
292 array($this, 'handler_destroy'),
293 array($this, 'handler_gc'));
294 if (!$result) {
eee3bd3f 295 print_error('dbsessionhandlerproblem', 'error');
0a2092a3 296 }
297 }
298
299 public function handler_open($save_path, $session_name) {
300 global $DB;
301
302 $this->database = $DB;
0a2092a3 303 return true;
304 }
305
306 public function handler_close() {
307 $this->record = null;
308 return true;
309 }
310
311 public function handler_read($sid) {
312 global $CFG;
313
0a2092a3 314 if ($this->record and $this->record->sid != $sid) {
315 error_log('Weird error reading session - mismatched sid');
316 return '';
317 }
318
319 try {
3b1a9849 320 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
321 $this->database->get_session_lock($record->id);
322
323 } else {
0a2092a3 324 $record = new object();
325 $record->state = 0;
326 $record->sid = $sid;
327 $record->sessdata = null;
17d93489 328 $record->sessdatahash = null;
0a2092a3 329 $record->userid = 0;
330 $record->timecreated = $record->timemodified = time();
331 $record->firstip = $record->lastip = getremoteaddr();
0a2092a3 332 $record->id = $this->database->insert_record_raw('sessions', $record);
333
3b1a9849 334 $this->database->get_session_lock($record->id);
0a2092a3 335 }
336 } catch (dml_exception $ex) {
337 if (!empty($CFG->rolesactive)) {
338 error_log('Can not read or insert database sessions');
339 }
340 return '';
341 }
342
3b1a9849 343 // verify timeout
344 if ($record->timemodified + $CFG->sessiontimeout < time()) {
345 // TODO: implement auth plugin timeout hook (see gc)
346 $record->state = 0;
347 $record->sessdata = null;
348 $record->sessdatahash = null;
349 $record->userid = 0;
350 $record->timecreated = $record->timemodified = time();
351 $record->firstip = $record->lastip = getremoteaddr();
eee3bd3f 352 try {
3b1a9849 353 $this->database->update_record('sessions', $record);
354 } catch (dml_exception $ex) {
a644e212 355 error_log('Can not time out database session');
3b1a9849 356 return '';
eee3bd3f 357 }
eee3bd3f 358 }
359
3b1a9849 360 if ($record->sessdatahash !== null) {
361 if (md5($record->sessdata) !== $record->sessdatahash) {
362 // probably this is caused by misconfigured mysql - the allowed request size might be too small
363 try {
364 $this->database->delete_records('sessions', array('sid'=>$record->sid));
365 } catch (dml_exception $ignored) {
366 }
367 print_error('dbsessionbroken', 'error');
368 }
369
370 $data = base64_decode($record->sessdata);
371 } else {
372 $data = '';
373 }
374
0a2092a3 375 unset($record->sessdata); // conserve memory
376 $this->record = $record;
377
378 return $data;
379 }
380
381 public function handler_write($sid, $session_data) {
382 global $USER;
383
384 if (!$this->record) {
385 error_log('Weird error writing session');
386 return true;
387 }
388
3b1a9849 389 $this->database->release_session_lock($this->record->id);
7f79aaea 390
0a2092a3 391 $this->record->sid = $sid; // it might be regenerated
392 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
17d93489 393 $this->record->sessdatahash = md5($this->record->sessdata);
0a2092a3 394 $this->record->userid = empty($USER->realuser) ? $USER->id : $USER->realuser;
395 $this->record->timemodified = time();
396 $this->record->lastip = getremoteaddr();
397
eee3bd3f 398 // TODO: verify session changed before doing update
399
0a2092a3 400 try {
401 $this->database->update_record_raw('sessions', $this->record);
402 } catch (dml_exception $ex) {
403 error_log('Can not write session to database.');
404 }
17d93489 405
0a2092a3 406 return true;
407 }
408
409 public function handler_destroy($sid) {
410 if (!$this->record or $this->record->sid != $sid) {
411 error_log('Weird error destroying session - mismatched sid');
412 return true;
413 }
414
3b1a9849 415 $this->database->release_session_lock($this->record->id);
7f79aaea 416
0a2092a3 417 try {
418 $this->database->delete_records('sessions', array('sid'=>$this->record->sid));
419 } catch (dml_exception $ex) {
420 error_log('Can not destroy database session.');
421 }
422
423 return true;
424 }
425
3b1a9849 426 public function handler_gc($ignored_maxlifetime) {
427 global $CFG;
428 $maxlifetime = $CFG->sessiontimeout;
429
0a2092a3 430 $select = "timemodified + :maxlifetime < :now";
431 $params = array('now'=>time(), 'maxlifetime'=>$maxlifetime);
432
7f79aaea 433 // TODO: add auth plugin hook that would allow extending of max lifetime
eee3bd3f 434
0a2092a3 435 try {
436 $this->database->delete_records_select('sessions', $select, $params);
437 } catch (dml_exception $ex) {
438 error_log('Can not garbage collect database sessions.');
439 }
440
441 return true;
57f7b7ce 442 }
0a2092a3 443
0ad6b20c 444}
57f7b7ce 445
0ad6b20c 446/**
447 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
448 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
449 * sesskey string if $USER exists, or boolean false if not.
450 *
451 * @uses $USER
452 * @return string
453 */
454function sesskey() {
455 global $USER;
57f7b7ce 456
0ad6b20c 457 if (empty($USER->sesskey)) {
458 $USER->sesskey = random_string(10);
459 }
57f7b7ce 460
0ad6b20c 461 return $USER->sesskey;
462}
57f7b7ce 463
57f7b7ce 464
0ad6b20c 465/**
466 * For security purposes, this function will check that the currently
467 * given sesskey (passed as a parameter to the script or this function)
468 * matches that of the current user.
469 *
470 * @param string $sesskey optionally provided sesskey
471 * @return bool
472 */
473function confirm_sesskey($sesskey=NULL) {
474 global $USER;
57f7b7ce 475
eb85959b 476 if (!empty($USER->ignoresesskey)) {
0ad6b20c 477 return true;
478 }
57f7b7ce 479
0ad6b20c 480 if (empty($sesskey)) {
481 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
57f7b7ce 482 }
483
eb85959b 484 return (sesskey() === $sesskey);
0ad6b20c 485}
486
487/**
488 * Sets a moodle cookie with a weakly encrypted string
489 *
490 * @uses $CFG
491 * @uses DAYSECS
492 * @uses HOURSECS
493 * @param string $thing The string to encrypt and place in a cookie
494 */
495function set_moodle_cookie($thing) {
496 global $CFG;
497
0a2092a3 498 if (NO_MOODLE_COOKIES) {
499 return;
500 }
501
0ad6b20c 502 if ($thing == 'guest') { // Ignore guest account
503 return;
57f7b7ce 504 }
505
0ad6b20c 506 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
507
508 $days = 60;
509 $seconds = DAYSECS*$days;
510
511 // no need to set secure or http cookie only here - it is not secret
512 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
513 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
514}
515
516/**
517 * Gets a moodle cookie with a weakly encrypted string
518 *
519 * @uses $CFG
520 * @return string
521 */
522function get_moodle_cookie() {
523 global $CFG;
524
0a2092a3 525 if (NO_MOODLE_COOKIES) {
526 return '';
527 }
528
0ad6b20c 529 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
530
531 if (empty($_COOKIE[$cookiename])) {
532 return '';
533 } else {
534 $thing = rc4decrypt($_COOKIE[$cookiename]);
535 return ($thing == 'guest') ? '': $thing; // Ignore guest account
57f7b7ce 536 }
0ad6b20c 537}
57f7b7ce 538
b7b64ff2 539/**
540 * Setup $USER object - called during login, loginas, etc.
541 * Preloads capabilities and checks enrolment plugins
542 *
543 * @param object $user full user record object
544 * @return void
545 */
546function session_set_user($user) {
547 $_SESSION['USER'] = $user;
548 check_enrolment_plugins($_SESSION['USER']);
549 load_all_capabilities();
eb85959b 550 sesskey(); // init session key
b7b64ff2 551}
552
542797b4 553/**
554 * Is current $USER logged-in-as somebody else?
555 * @return bool
556 */
b7b64ff2 557function session_is_loggedinas() {
85f6b737 558 return !empty($_SESSION['USER']->realuser);
542797b4 559}
560
6132768e 561/**
562 * Returns the $USER object ignoring current login-as session
563 * @return object user object
564 */
b7b64ff2 565function session_get_realuser() {
566 if (session_is_loggedinas()) {
6132768e 567 return $_SESSION['REALUSER'];
568 } else {
569 return $_SESSION['USER'];
570 }
571}
572
542797b4 573/**
574 * Login as another user - no security checks here.
575 * @param int $userid
576 * @param object $context
577 * @return void
578 */
8d1964c4 579function session_loginas($userid, $context) {
b7b64ff2 580 if (session_is_loggedinas()) {
8d1964c4 581 return;
582 }
583
85f6b737 584 // switch to fresh new $SESSION
585 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
6132768e 586 $_SESSION['SESSION'] = new object();
8d1964c4 587
85f6b737 588 /// Create the new $USER object with all details and reload needed capabilitites
589 $_SESSION['REALUSER'] = $_SESSION['USER'];
b7b64ff2 590 $user = get_complete_user_data('id', $userid);
591 $user->realuser = $_SESSION['REALUSER']->id;
592 $user->loginascontext = $context;
593 session_set_user($user);
8d1964c4 594}
595
542797b4 596/**
597 * Terminate login-as session
598 * @return void
599 */
8d1964c4 600function session_unloginas() {
b7b64ff2 601 if (!session_is_loggedinas()) {
8d1964c4 602 return;
603 }
604
6132768e 605 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
606 unset($_SESSION['REALSESSION']);
8d1964c4 607
6132768e 608 $_SESSION['USER'] = $_SESSION['REALUSER'];
609 unset($_SESSION['REALUSER']);
8d1964c4 610}
611
e8b7114d 612/**
613 * Sets up current user and course enviroment (lang, etc.) in cron.
614 * Do not use outside of cron script!
615 *
616 * @param object $user full user object, null means default cron user (admin)
617 * @param $course full course record, null means $SITE
618 * @return void
619 */
620function cron_setup_user($user=null, $course=null) {
621 global $CFG, $SITE;
622
623 static $cronuser = null;
624 static $cronsession = null;
625
626 if (empty($cronuser)) {
627 /// ignore admins timezone, language and locale - use site deafult instead!
628 $cronuser = get_admin();
629 $cronuser->timezone = $CFG->timezone;
630 $cronuser->lang = '';
631 $cronuser->theme = '';
632
633 $cronsession = array();
634 }
635
636 if (!$user) {
637 // cached default cron user (==modified admin for now)
638 session_set_user($cronuser);
639 $_SESSION['SESSION'] = $cronsession;
640
641 } else {
642 // emulate real user session - needed for caps in cron
643 if ($_SESSION['USER']->id != $user->id) {
644 session_set_user($user);
645 $_SESSION['SESSION'] = array();
646 }
647 }
648
649 if ($course) {
650 course_setup($course);
651 } else {
652 course_setup($SITE);
653 }
654
655 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
656
657}
658
0ad6b20c 659/**
660* Enable cookieless sessions by including $CFG->usesid=true;
661* in config.php.
662* Based on code from php manual by Richard at postamble.co.uk
663* 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.
664* 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.
665* This doesn't require trans_sid to be turned on but this is recommended for better performance
666* you should put :
667* session.use_trans_sid = 1
668* in your php.ini file and make sure that you don't have a line like this in your php.ini
669* session.use_only_cookies = 1
670* @author Richard at postamble.co.uk and Jamie Pratt
671* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
672*/
673/**
674* You won't call this function directly. This function is used to process
675* text buffered by php in an output buffer. All output is run through this function
676* before it is ouput.
677* @param string $buffer is the output sent from php
678* @return string the output sent to the browser
679*/
680function sid_ob_rewrite($buffer){
681 $replacements = array(
682 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
683 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
684
685 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
686 $buffer = preg_replace('/<form\s[^>]*>/i',
687 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
688
689 return $buffer;
690}
691/**
692* You won't call this function directly. This function is used to process
693* text buffered by php in an output buffer. All output is run through this function
694* before it is ouput.
695* This function only processes absolute urls, it is used when we decide that
696* php is processing other urls itself but needs some help with internal absolute urls still.
697* @param string $buffer is the output sent from php
698* @return string the output sent to the browser
699*/
700function sid_ob_rewrite_absolute($buffer){
701 $replacements = array(
702 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
703 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
704
705 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
706 $buffer = preg_replace('/<form\s[^>]*>/i',
707 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
708 return $buffer;
709}
57f7b7ce 710
0ad6b20c 711/**
712* A function to process link, a and script tags found
713* by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
714*/
715function sid_rewrite_link_tag($matches){
716 $url = $matches[4];
717 $url = sid_process_url($url);
718 return $matches[1].$url.$matches[5];
719}
720
721/**
722* You can call this function directly. This function is used to process
723* urls to add a moodle session id to the url for internal links.
724* @param string $url is a url
725* @return string the processed url
726*/
727function sid_process_url($url) {
728 global $CFG;
729
730 if ((preg_match('/^(http|https):/i', $url)) // absolute url
731 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
732 return $url; //don't attach sessid to non local urls
733 }
734 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
735 return $url; //don't attach sessid to anchors
736 }
737 if (strpos($url, session_name())!==FALSE) {
738 return $url; //don't attach sessid to url that already has one sessid
739 }
740 if (strpos($url, "?")===FALSE) {
741 $append = "?".strip_tags(session_name() . '=' . session_id());
742 } else {
743 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
57f7b7ce 744 }
0ad6b20c 745 //put sessid before any anchor
746 $p = strpos($url, "#");
747 if ($p!==FALSE){
748 $anch = substr($url, $p);
749 $url = substr($url, 0, $p).$append.$anch ;
750 } else {
751 $url .= $append ;
752 }
753 return $url;
754}
57f7b7ce 755
0ad6b20c 756/**
757* Call this function before there has been any output to the browser to
758* buffer output and add session ids to all internal links.
759*/
760function sid_start_ob(){
761 global $CFG;
762 //don't attach sess id for bots
763
764 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
765 if (!empty($CFG->opentogoogle)) {
766 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
767 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
768 $CFG->usesid=false;
769 return;
57f7b7ce 770 }
0ad6b20c 771 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
57f7b7ce 772 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
773 $CFG->usesid=false;
774 return;
775 }
776 }
0ad6b20c 777 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
778 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
779 $CFG->usesid=false;
780 return;
781 }
782 }
57f7b7ce 783
0ad6b20c 784 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
57f7b7ce 785
0ad6b20c 786 if (ini_get('session.use_trans_sid') != 0) {
787 // use trans sid as its available
788 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
789 ob_start('sid_ob_rewrite_absolute');
790 } else {
791 //rewrite all links ourselves
792 ob_start('sid_ob_rewrite');
57f7b7ce 793 }
e6e13284 794}
0ad6b20c 795