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