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