[ Index ] |
|
Code source de b2evolution 2.1.0-beta |
1 <?php 2 /** 3 * This file implements the Session class and holds the 4 * {@link session_unserialize_callback()} function used by it. 5 * 6 * A session can be bound to a user and provides functions to store data in its 7 * context. 8 * All Hitlogs are also bound to a Session. 9 * 10 * This file is part of the evoCore framework - {@link http://evocore.net/} 11 * See also {@link http://sourceforge.net/projects/evocms/}. 12 * 13 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/} 14 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. 15 * 16 * {@internal License choice 17 * - If you have received this file as part of a package, please find the license.txt file in 18 * the same folder or the closest folder above for complete license terms. 19 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) 20 * then you must choose one of the following licenses before using the file: 21 * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php 22 * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php 23 * }} 24 * 25 * {@internal Open Source relicensing agreement: 26 * Daniel HAHLER grants Francois PLANQUE the right to license 27 * Daniel HAHLER's contributions to this file and the b2evolution project 28 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 29 * 30 * Matt FOLLETT grants Francois PLANQUE the right to license 31 * Matt FOLLETT's contributions to this file and the b2evolution project 32 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 33 * }} 34 * 35 * @package evocore 36 * 37 * {@internal Below is a list of authors who have contributed to design/coding of this file: }} 38 * @author blueyed: Daniel HAHLER. 39 * @author fplanque: Francois PLANQUE. 40 * @author jeffbearer: Jeff BEARER - {@link http://www.jeffbearer.com/}. 41 * @author mfollett: Matt FOLLETT - {@link http://www.mfollett.com/}. 42 * 43 * @version $Id: _session.class.php,v 1.1 2007/06/25 11:01:00 fplanque Exp $ 44 */ 45 if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); 46 47 48 /** 49 * A session tracks a given user (not necessarily logged in) while he's navigating the site. 50 * A sessions also stores data for the length of the session. 51 * 52 * Sessions are tracked with a cookie containing the session ID. 53 * The cookie also contains a random key to prevent sessions hacking. 54 * 55 * @package evocore 56 */ 57 class Session 58 { 59 /** 60 * The ID of the session. 61 * @var integer 62 */ 63 var $ID; 64 65 /** 66 * The session key (to be used in URLs). 67 * @var string 68 */ 69 var $key; 70 71 /** 72 * The user ID for the user of the session (NULL for anonymous (not logged in) user). 73 * 74 * @var integer 75 */ 76 var $user_ID; 77 78 /** 79 * Is the session validated? 80 * This means that it was created from a received cookie. 81 * @var boolean 82 */ 83 var $is_validated = false; 84 85 /** 86 * Data stored for the session. 87 * 88 * This holds an array( expire, value ) for each data item key. 89 * 90 * @access protected 91 * @var array 92 */ 93 var $_data; 94 95 var $_session_needs_save = false; 96 97 98 /** 99 * Constructor 100 */ 101 function Session() 102 { 103 global $DB, $Debuglog, $current_User, $localtimenow, $Messages, $Settings; 104 global $Hit; 105 global $cookie_session, $cookie_expires, $cookie_path, $cookie_domain; 106 107 $Debuglog->add( 'cookie_domain='.$cookie_domain, 'session' ); 108 $Debuglog->add( 'cookie_path='.$cookie_path, 'session' ); 109 110 $session_cookie = param_cookie( $cookie_session, 'string', '' ); 111 if( empty( $session_cookie ) ) 112 { 113 $Debuglog->add( 'No session cookie received.', 'session' ); 114 } 115 else 116 { // session ID sent by cookie 117 if( ! preg_match( '~^(\d+)_(\w+)$~', $session_cookie, $match ) ) 118 { 119 $Debuglog->add( 'Invalid session cookie format!', 'session' ); 120 } 121 else 122 { // We have a valid session cookie: 123 $session_id_by_cookie = $match[1]; 124 $session_key_by_cookie = $match[2]; 125 126 $Debuglog->add( 'Session ID received from cookie: '.$session_id_by_cookie, 'session' ); 127 128 $row = $DB->get_row( ' 129 SELECT sess_ID, sess_key, sess_data, sess_user_ID 130 FROM T_sessions 131 WHERE sess_ID = '.$DB->quote($session_id_by_cookie).' 132 AND sess_key = '.$DB->quote($session_key_by_cookie).' 133 AND UNIX_TIMESTAMP(sess_lastseen) > '.($localtimenow - $Settings->get('timeout_sessions')) ); 134 if( empty( $row ) ) 135 { 136 $Debuglog->add( 'Session ID/key combination is invalid!', 'session' ); 137 } 138 else 139 { // ID + key are valid: load data 140 $Debuglog->add( 'Session ID is valid.', 'session' ); 141 $this->ID = $row->sess_ID; 142 $this->key = $row->sess_key; 143 $this->user_ID = $row->sess_user_ID; 144 $this->is_validated = true; 145 146 $Debuglog->add( 'Session user_ID: '.var_export($this->user_ID, true), 'session' ); 147 148 if( empty( $row->sess_data ) ) 149 { 150 $Debuglog->add( 'No session data available.', 'session' ); 151 $this->_data = array(); 152 } 153 else 154 { // Some session data has been previsouly stored: 155 156 // Unserialize session data (using an own callback that should provide class definitions): 157 $old_callback = ini_set( 'unserialize_callback_func', 'session_unserialize_callback' ); 158 if( $old_callback === false ) 159 { // this can fail, if "ini_set" has been disabled for security reasons.. :/ 160 // Brutally load add classes that we might need: 161 session_unserialize_load_all_classes(); 162 } 163 // TODO: dh> This can fail, if there are special chars in sess_data: 164 // It will be encoded in $evo_charset _after_ "SET NAMES", but 165 // get retrieved here, _before_ any "SET NAMES" (if $db_config['connection_charset'] is not set (default))! 166 $this->_data = @unserialize($row->sess_data); 167 168 if( $old_callback !== false ) 169 { // Restore the old callback if we changed it: 170 ini_set( 'unserialize_callback_func', $old_callback ); 171 } 172 173 if( ! is_array($this->_data) ) 174 { 175 $Debuglog->add( 'Session data corrupted!<br /> 176 connection_charset: '.var_export($DB->connection_charset, true).'<br /> 177 Serialized data was: --['.var_export($row->sess_data, true).']--', array('session','error') ); 178 $this->_data = array(); 179 } 180 else 181 { 182 $Debuglog->add( 'Session data loaded.', 'session' ); 183 184 // Load a Messages object from session data, if available: 185 if( ($sess_Messages = $this->get('Messages')) && is_a( $sess_Messages, 'log' ) ) 186 { 187 // dh> TODO: "old" messages should rather get prepended to any existing ones from the current request, rather than appended 188 $Messages->add_messages( $sess_Messages->messages ); 189 $Debuglog->add( 'Added Messages from session data.', 'session' ); 190 $this->delete( 'Messages' ); 191 } 192 } 193 } 194 } 195 } 196 } 197 198 199 if( $this->ID ) 200 { // there was a valid session before; update data (lastseen) 201 $this->_session_needs_save = true; 202 } 203 else 204 { // create a new session 205 $this->key = generate_random_key(32); 206 207 // We need to INSERT now because we need an ID now! (for the cookie) 208 $DB->query( " 209 INSERT INTO T_sessions( sess_key, sess_lastseen, sess_ipaddress ) 210 VALUES ( 211 '".$this->key."', 212 '".date( 'Y-m-d H:i:s', $localtimenow )."', 213 '".$Hit->IP."' 214 )" ); 215 216 $this->ID = $DB->insert_id; 217 218 // Set a cookie valid for ~ 10 years: 219 setcookie( $cookie_session, $this->ID.'_'.$this->key, time()+315360000, $cookie_path, $cookie_domain ); 220 221 $Debuglog->add( 'ID (generated): '.$this->ID, 'session' ); 222 $Debuglog->add( 'Cookie sent.', 'session' ); 223 } 224 225 register_shutdown_function( array( & $this, 'dbsave' ) ); 226 } 227 228 229 /** 230 * Attach a User object to the session. 231 * 232 * @param User The user to attach 233 */ 234 function set_User( $User ) 235 { 236 return $this->set_user_ID( $User->ID ); 237 } 238 239 240 /** 241 * Attach a user ID to the session. 242 * 243 * NOTE: ID gets saved to DB on shutdown. This may be a "problem" when querying T_sessions for sess_user_ID. 244 * 245 * @param integer The ID of the user to attach 246 */ 247 function set_user_ID( $user_ID ) 248 { 249 if( $user_ID != $this->user_ID ) 250 { 251 global $UserSettings, $DB; 252 253 if( ! $UserSettings->get('login_multiple_sessions', $user_ID) ) 254 { // The user does not want to have multiple sessions open at the same time: 255 // Invalidate previous sessions: 256 global $Debuglog; 257 $Debuglog->add( 'Invalidating all previous user sessions, because login_multiple_sessions=0', 'session' ); 258 $DB->query( ' 259 UPDATE T_sessions 260 SET sess_key = NULL 261 WHERE sess_user_ID = '.$DB->quote($user_ID).' 262 AND sess_ID != '.$this->ID ); 263 } 264 265 $this->user_ID = $user_ID; 266 $this->_session_needs_save = true; 267 } 268 } 269 270 271 /** 272 * Logout the user, by invalidating the session key and unsetting {@link $user_ID}. 273 * 274 * We want to keep the user in the session log, but we're unsetting {@link $user_ID}, which refers 275 * to the current session. 276 * 277 * Because the session key is invalid/broken, on the next request a new session will be started. 278 * 279 * NOTE: we MIGHT want to link subsequent sessions together if we want to keep track... 280 */ 281 function logout() 282 { 283 global $Debuglog, $cookie_session, $cookie_path, $cookie_domain; 284 285 // Invalidate the session key (no one will be able to use this session again) 286 $this->key = NULL; 287 $this->_data = array(); // We don't need to keep old data 288 $this->_session_needs_save = true; 289 $this->dbsave(); 290 291 $this->user_ID = NULL; // Unset user_ID after invalidating/saving the session above, to keep the user info attached to the old session. 292 293 // clean up the session cookie: 294 setcookie( $cookie_session, '', 200000000, $cookie_path, $cookie_domain ); 295 } 296 297 298 /** 299 * Check if session has a user attached. 300 * 301 * @return boolean 302 */ 303 function has_User() 304 { 305 return !empty( $this->user_ID ); 306 } 307 308 309 /** 310 * Get the attached User. 311 * 312 * @return false|User 313 */ 314 function & get_User() 315 { 316 if( !empty($this->user_ID) ) 317 { 318 $UserCache = & get_Cache( 'UserCache' ); 319 return $UserCache->get_by_ID( $this->user_ID ); 320 } 321 322 $r = false; 323 return $r; 324 } 325 326 327 /** 328 * Get a data value for the session. This checks for the data to be expired and unsets it then. 329 * 330 * @param string Name of the data's key. 331 * @param mixed Default value to use if key is not set or has expired. (since 1.10.0) 332 * @return mixed The value, if set; otherwise $default 333 */ 334 function get( $param, $default = NULL ) 335 { 336 global $Debuglog, $localtimenow; 337 338 if( isset( $this->_data[$param] ) ) 339 { 340 if( array_key_exists(1, $this->_data[$param]) // can be NULL! 341 && ( is_null( $this->_data[$param][0] ) || $this->_data[$param][0] > $localtimenow ) ) // check for expired data 342 { 343 return $this->_data[$param][1]; 344 } 345 else 346 { // expired or old format (without 'value' key) 347 unset( $this->_data[$param] ); 348 $this->_session_needs_save = true; 349 $Debuglog->add( 'Session data['.$param.'] expired.', 'session' ); 350 } 351 } 352 353 return $default; 354 } 355 356 357 /** 358 * Set a data value for the session. 359 * 360 * @param string Name of the data's key. 361 * @param mixed The value 362 * @param integer Time in seconds for data to expire (0 to disable). 363 */ 364 function set( $param, $value, $expire = 0 ) 365 { 366 global $Debuglog, $localtimenow; 367 368 if( ! isset($this->_data[$param]) 369 || ! is_array($this->_data[$param]) // deprecated: check to transform 1.6 session data to 1.7 370 || $this->_data[$param][1] != $value 371 || $expire != 0 ) 372 { // There is something to update: 373 $this->_data[$param] = array( ( $expire ? ($localtimenow + $expire) : NULL ), $value ); 374 375 if( $param == 'Messages' ) 376 { // also set boolean to not call CachePageContent plugin event on next request: 377 $this->set( 'core.no_CachePageContent', 1 ); 378 } 379 380 $Debuglog->add( 'Session data['.$param.'] updated. Expire in: '.( $expire ? $expire.'s' : '-' ).'.', 'session' ); 381 382 $this->_session_needs_save = true; 383 } 384 } 385 386 387 /** 388 * Delete a value from the session data. 389 * 390 * @param string Name of the data's key. 391 */ 392 function delete( $param ) 393 { 394 global $Debuglog; 395 396 if( isset($this->_data[$param]) ) 397 { 398 unset( $this->_data[$param] ); 399 400 $Debuglog->add( 'Session data['.$param.'] deleted!', 'session' ); 401 402 $this->_session_needs_save = true; 403 } 404 } 405 406 407 /** 408 * Updates session data in database. 409 * 410 * Note: The key actually only needs to be updated on a logout. 411 */ 412 function dbsave() 413 { 414 global $DB, $Debuglog, $Hit, $localtimenow; 415 416 if( ! $this->_session_needs_save ) 417 { // There have been no changes since the last save. 418 return false; 419 } 420 421 $sess_data = empty($this->_data) ? NULL : serialize($this->_data); 422 $DB->query( " 423 UPDATE T_sessions SET 424 sess_data = ".$DB->quote( $sess_data ).", 425 sess_ipaddress = '".$Hit->IP."', 426 sess_key = ".$DB->quote( $this->key ).", 427 sess_lastseen = '".date( 'Y-m-d H:i:s', $localtimenow )."', 428 sess_user_ID = ".$DB->null( $this->user_ID )." 429 WHERE sess_ID = ".$this->ID, 'Session::dbsave()' ); 430 431 $Debuglog->add( 'Session data saved!', 'session' ); 432 433 $this->_session_needs_save = false; 434 } 435 436 437 /** 438 * Reload session data. 439 * 440 * This is needed if the running process waits for a child process to write data 441 * into the Session, e.g. the captcha plugin in test mode waiting for the Debuglog 442 * output from the process that created the image (included through an IMG tag). 443 */ 444 function reload_data() 445 { 446 global $Debuglog, $DB; 447 448 if( empty($this->ID) ) 449 { 450 return false; 451 } 452 453 $sess_data = $DB->get_var( ' 454 SELECT sess_data FROM T_sessions 455 WHERE sess_ID = '.$this->ID ); 456 457 $sess_data = @unserialize( $sess_data ); 458 if( $sess_data === false ) 459 { 460 $this->_data = array(); 461 } 462 else 463 { 464 $this->_data = $sess_data; 465 } 466 467 $Debuglog->add( 'Reloaded session data.' ); 468 } 469 } 470 471 472 /** 473 * This gets used as a {@link unserialize()} callback function, which is 474 * responsible for loading the requested class. 475 * 476 * IMPORTANT: when modifying this, modify the following also: 477 * @see session_unserialize_load_all_classes() 478 * 479 * @todo Once we require PHP5, we should think about using this as __autoload function. 480 * 481 * @return boolean True, if the required class could be loaded; false, if not 482 */ 483 function session_unserialize_callback( $classname ) 484 { 485 switch( strtolower($classname) ) 486 { 487 case 'blog': 488 load_class('collections/model/_blog.class.php'); 489 return true; 490 491 case 'collectionsettings': 492 load_class('collections/model/_collsettings.class.php'); 493 return true; 494 495 case 'comment': 496 load_class('comments/model/_comment.class.php'); 497 return true; 498 499 case 'item': 500 load_class('items/model/_item.class.php'); 501 return true; 502 503 case 'group': 504 load_class('users/model/_group.class.php'); 505 return true; 506 507 case 'user': 508 load_class('users/model/_user.class.php'); 509 return true; 510 } 511 512 return false; 513 } 514 515 516 /** 517 * When session_unserialize_callback() cannot be registered to do some smart loading, 518 * then we fall back to this function and load everything with brute force... 519 * 520 * IMPORTANT: when modifying this, modify the following also: 521 * @see session_unserialize_callback() 522 */ 523 function session_unserialize_load_all_classes() 524 { 525 load_class('collections/model/_blog.class.php'); 526 load_class('collections/model/_collsettings.class.php'); 527 load_class('comments/model/_comment.class.php'); 528 load_class('items/model/_item.class.php'); 529 load_class('users/model/_group.class.php'); 530 load_class('users/model/_user.class.php'); 531 } 532 533 534 /* 535 * $Log: _session.class.php,v $ 536 * Revision 1.1 2007/06/25 11:01:00 fplanque 537 * MODULES (refactored MVC) 538 * 539 * Revision 1.43 2007/06/19 22:50:15 blueyed 540 * cleanup 541 * 542 * Revision 1.42 2007/05/13 22:02:09 fplanque 543 * removed bloated $object_def 544 * 545 * Revision 1.41 2007/04/26 00:11:11 fplanque 546 * (c) 2007 547 * 548 * Revision 1.40 2007/04/23 15:08:39 blueyed 549 * TODO 550 * 551 * Revision 1.39 2007/04/05 21:53:51 fplanque 552 * fix for OVH 553 * 554 * Revision 1.38 2007/03/11 18:29:50 blueyed 555 * Use is_array for session data check 556 * 557 * Revision 1.37 2007/02/25 01:39:05 fplanque 558 * wording 559 * 560 * Revision 1.36 2007/02/21 22:21:30 blueyed 561 * "Multiple sessions" user setting 562 * 563 * Revision 1.35 2007/02/15 16:37:53 waltercruz 564 * Changing double quotes to single quotes 565 * 566 * Revision 1.34 2007/02/14 14:38:04 waltercruz 567 * Changing double quotes to single quotes 568 * 569 * Revision 1.33 2007/01/27 15:18:23 blueyed 570 * doc 571 * 572 * Revision 1.32 2007/01/27 01:02:49 blueyed 573 * debug_die() if ini_set() fails on Session data restore 574 * 575 * Revision 1.31 2007/01/16 00:08:44 blueyed 576 * Implemented $default param for Session::get() 577 * 578 * Revision 1.30 2006/12/28 15:43:31 fplanque 579 * minor 580 * 581 * Revision 1.29 2006/12/17 23:44:35 fplanque 582 * minor cleanup 583 * 584 * Revision 1.28 2006/12/07 23:13:11 fplanque 585 * @var needs to have only one argument: the variable type 586 * Otherwise, I can't code! 587 * 588 * Revision 1.27 2006/11/24 18:27:24 blueyed 589 * Fixed link to b2evo CVS browsing interface in file docblocks 590 * 591 * Revision 1.26 2006/11/14 21:13:58 blueyed 592 * I've spent > 2 hours debugging this charset nightmare and all I've got are those lousy TODOs.. 593 */ 594 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |