| 1 |
<?php
|
| 2 |
// $Id: session.inc,v 1.72 2009/09/28 22:22:54 dries Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* User session handling functions.
|
| 7 |
*
|
| 8 |
* The user-level session storage handlers:
|
| 9 |
* - _drupal_session_open()
|
| 10 |
* - _drupal_session_close()
|
| 11 |
* - _drupal_session_read()
|
| 12 |
* - _drupal_session_write()
|
| 13 |
* - _drupal_session_destroy()
|
| 14 |
* - _drupal_session_garbage_collection()
|
| 15 |
* are assigned by session_set_save_handler() in bootstrap.inc and are called
|
| 16 |
* automatically by PHP. These functions should not be called directly. Session
|
| 17 |
* data should instead be accessed via the $_SESSION superglobal.
|
| 18 |
*/
|
| 19 |
|
| 20 |
/**
|
| 21 |
* Session handler assigned by session_set_save_handler().
|
| 22 |
*
|
| 23 |
* This function is used to handle any initialization, such as file paths or
|
| 24 |
* database connections, that is needed before accessing session data. Drupal
|
| 25 |
* does not need to initialize anything in this function.
|
| 26 |
*
|
| 27 |
* This function should not be called directly.
|
| 28 |
*
|
| 29 |
* @return
|
| 30 |
* This function will always return TRUE.
|
| 31 |
*/
|
| 32 |
function _drupal_session_open() {
|
| 33 |
return TRUE;
|
| 34 |
}
|
| 35 |
|
| 36 |
/**
|
| 37 |
* Session handler assigned by session_set_save_handler().
|
| 38 |
*
|
| 39 |
* This function is used to close the current session. Because Drupal stores
|
| 40 |
* session data in the database immediately on write, this function does
|
| 41 |
* not need to do anything.
|
| 42 |
*
|
| 43 |
* This function should not be called directly.
|
| 44 |
*
|
| 45 |
* @return
|
| 46 |
* This function will always return TRUE.
|
| 47 |
*/
|
| 48 |
function _drupal_session_close() {
|
| 49 |
return TRUE;
|
| 50 |
}
|
| 51 |
|
| 52 |
/**
|
| 53 |
* Session handler assigned by session_set_save_handler().
|
| 54 |
*
|
| 55 |
* This function will be called by PHP to retrieve the current user's
|
| 56 |
* session data, which is stored in the database. It also loads the
|
| 57 |
* current user's appropriate roles into the user object.
|
| 58 |
*
|
| 59 |
* This function should not be called directly. Session data should
|
| 60 |
* instead be accessed via the $_SESSION superglobal.
|
| 61 |
*
|
| 62 |
* @param $sid
|
| 63 |
* Session ID.
|
| 64 |
* @return
|
| 65 |
* Either an array of the session data, or an empty string, if no data
|
| 66 |
* was found or the user is anonymous.
|
| 67 |
*/
|
| 68 |
function _drupal_session_read($sid) {
|
| 69 |
global $user, $is_https;
|
| 70 |
|
| 71 |
// Write and Close handlers are called after destructing objects
|
| 72 |
// since PHP 5.0.5.
|
| 73 |
// Thus destructors can use sessions but session handler can't use objects.
|
| 74 |
// So we are moving session closure before destructing objects.
|
| 75 |
register_shutdown_function('session_write_close');
|
| 76 |
|
| 77 |
// Handle the case of first time visitors and clients that don't store
|
| 78 |
// cookies (eg. web crawlers).
|
| 79 |
$insecure_session_name = substr(session_name(), 1);
|
| 80 |
if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) {
|
| 81 |
$user = drupal_anonymous_user();
|
| 82 |
return '';
|
| 83 |
}
|
| 84 |
|
| 85 |
// Otherwise, if the session is still active, we have a record of the
|
| 86 |
// client's session in the database. If it's HTTPS then we are either have
|
| 87 |
// a HTTPS session or we are about to log in so we check the sessions table
|
| 88 |
// for an anonymous session wth the non-HTTPS-only cookie.
|
| 89 |
if ($is_https) {
|
| 90 |
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject();
|
| 91 |
if (!$user) {
|
| 92 |
if (isset($_COOKIE[$insecure_session_name])) {
|
| 93 |
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array(
|
| 94 |
':sid' => $_COOKIE[$insecure_session_name]))
|
| 95 |
->fetchObject();
|
| 96 |
}
|
| 97 |
}
|
| 98 |
}
|
| 99 |
else {
|
| 100 |
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject();
|
| 101 |
}
|
| 102 |
|
| 103 |
// We found the client's session record and they are an authenticated user.
|
| 104 |
if ($user && $user->uid > 0) {
|
| 105 |
// This is done to unserialize the data member of $user.
|
| 106 |
$user = drupal_unpack($user);
|
| 107 |
|
| 108 |
// Add roles element to $user.
|
| 109 |
$user->roles = array();
|
| 110 |
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
|
| 111 |
$user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1);
|
| 112 |
}
|
| 113 |
// We didn't find the client's record (session has expired), or they
|
| 114 |
// are an anonymous user.
|
| 115 |
else {
|
| 116 |
$session = isset($user->session) ? $user->session : '';
|
| 117 |
$user = drupal_anonymous_user($session);
|
| 118 |
}
|
| 119 |
|
| 120 |
return $user->session;
|
| 121 |
}
|
| 122 |
|
| 123 |
/**
|
| 124 |
* Session handler assigned by session_set_save_handler().
|
| 125 |
*
|
| 126 |
* This function will be called by PHP to store the current user's
|
| 127 |
* session, which Drupal saves to the database.
|
| 128 |
*
|
| 129 |
* This function should not be called directly. Session data should
|
| 130 |
* instead be accessed via the $_SESSION superglobal.
|
| 131 |
*
|
| 132 |
* @param $sid
|
| 133 |
* Session ID.
|
| 134 |
* @param $value
|
| 135 |
* Serialized array of the session data.
|
| 136 |
* @return
|
| 137 |
* This function will always return TRUE.
|
| 138 |
*/
|
| 139 |
function _drupal_session_write($sid, $value) {
|
| 140 |
global $user, $is_https;
|
| 141 |
|
| 142 |
if (!drupal_save_session()) {
|
| 143 |
// We don't have anything to do if we are not allowed to save the session.
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
|
| 147 |
$fields = array(
|
| 148 |
'uid' => $user->uid,
|
| 149 |
'cache' => isset($user->cache) ? $user->cache : 0,
|
| 150 |
'hostname' => ip_address(),
|
| 151 |
'session' => $value,
|
| 152 |
'timestamp' => REQUEST_TIME,
|
| 153 |
);
|
| 154 |
$key = array('sid' => $sid);
|
| 155 |
if ($is_https) {
|
| 156 |
$key['ssid'] = $sid;
|
| 157 |
$insecure_session_name = substr(session_name(), 1);
|
| 158 |
// The "secure pages" setting allows a site to simultaneously use both
|
| 159 |
// secure and insecure session cookies. If enabled, use the insecure session
|
| 160 |
// identifier as the sid.
|
| 161 |
if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
|
| 162 |
$key['sid'] = $_COOKIE[$insecure_session_name];
|
| 163 |
}
|
| 164 |
}
|
| 165 |
db_merge('sessions')
|
| 166 |
->key($key)
|
| 167 |
->fields($fields)
|
| 168 |
->execute();
|
| 169 |
|
| 170 |
// Last access time is updated no more frequently than once every 180 seconds.
|
| 171 |
// This reduces contention in the users table.
|
| 172 |
if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
|
| 173 |
db_update('users')
|
| 174 |
->fields(array(
|
| 175 |
'access' => REQUEST_TIME
|
| 176 |
))
|
| 177 |
->condition('uid', $user->uid)
|
| 178 |
->execute();
|
| 179 |
}
|
| 180 |
|
| 181 |
return TRUE;
|
| 182 |
}
|
| 183 |
|
| 184 |
/**
|
| 185 |
* Initialize the session handler, starting a session if needed.
|
| 186 |
*/
|
| 187 |
function drupal_session_initialize() {
|
| 188 |
global $user;
|
| 189 |
|
| 190 |
session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
|
| 191 |
|
| 192 |
if (isset($_COOKIE[session_name()])) {
|
| 193 |
// If a session cookie exists, initialize the session. Otherwise the
|
| 194 |
// session is only started on demand in drupal_session_commit(), making
|
| 195 |
// anonymous users not use a session cookie unless something is stored in
|
| 196 |
// $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
|
| 197 |
drupal_session_start();
|
| 198 |
if (!empty($user->uid) || !empty($_SESSION)) {
|
| 199 |
drupal_page_is_cacheable(FALSE);
|
| 200 |
}
|
| 201 |
}
|
| 202 |
else {
|
| 203 |
// Set a session identifier for this request. This is necessary because
|
| 204 |
// we lazyly start sessions at the end of this request, and some
|
| 205 |
// processes (like drupal_get_token()) needs to know the future
|
| 206 |
// session ID in advance.
|
| 207 |
$user = drupal_anonymous_user();
|
| 208 |
session_id(md5(uniqid('', TRUE)));
|
| 209 |
}
|
| 210 |
}
|
| 211 |
|
| 212 |
/**
|
| 213 |
* Forcefully start a session, preserving already set session data.
|
| 214 |
*
|
| 215 |
* @ingroup php_wrappers
|
| 216 |
*/
|
| 217 |
function drupal_session_start() {
|
| 218 |
if (!drupal_session_started()) {
|
| 219 |
// Save current session data before starting it, as PHP will destroy it.
|
| 220 |
$session_data = isset($_SESSION) ? $_SESSION : NULL;
|
| 221 |
|
| 222 |
session_start();
|
| 223 |
drupal_session_started(TRUE);
|
| 224 |
|
| 225 |
// Restore session data.
|
| 226 |
if (!empty($session_data)) {
|
| 227 |
$_SESSION += $session_data;
|
| 228 |
}
|
| 229 |
}
|
| 230 |
}
|
| 231 |
|
| 232 |
/**
|
| 233 |
* Commit the current session, if necessary.
|
| 234 |
*
|
| 235 |
* If an anonymous user already have an empty session, destroy it.
|
| 236 |
*/
|
| 237 |
function drupal_session_commit() {
|
| 238 |
global $user;
|
| 239 |
|
| 240 |
if (!drupal_save_session()) {
|
| 241 |
// We don't have anything to do if we are not allowed to save the session.
|
| 242 |
return;
|
| 243 |
}
|
| 244 |
|
| 245 |
if (empty($user->uid) && empty($_SESSION)) {
|
| 246 |
// There is no session data to store, destroy the session if it was
|
| 247 |
// previously started.
|
| 248 |
if (drupal_session_started()) {
|
| 249 |
session_destroy();
|
| 250 |
}
|
| 251 |
}
|
| 252 |
else {
|
| 253 |
// There is session data to store. Start the session if it is not already
|
| 254 |
// started.
|
| 255 |
if (!drupal_session_started()) {
|
| 256 |
drupal_session_start();
|
| 257 |
}
|
| 258 |
// Write the session data.
|
| 259 |
session_write_close();
|
| 260 |
}
|
| 261 |
}
|
| 262 |
|
| 263 |
/**
|
| 264 |
* Return whether a session has been started.
|
| 265 |
*/
|
| 266 |
function drupal_session_started($set = NULL) {
|
| 267 |
static $session_started = FALSE;
|
| 268 |
if (isset($set)) {
|
| 269 |
$session_started = $set;
|
| 270 |
}
|
| 271 |
return $session_started && session_id();
|
| 272 |
}
|
| 273 |
|
| 274 |
/**
|
| 275 |
* Called when an anonymous user becomes authenticated or vice-versa.
|
| 276 |
*
|
| 277 |
* @ingroup php_wrappers
|
| 278 |
*/
|
| 279 |
function drupal_session_regenerate() {
|
| 280 |
global $user, $is_https;
|
| 281 |
if ($is_https && variable_get('https', FALSE)) {
|
| 282 |
$insecure_session_name = substr(session_name(), 1);
|
| 283 |
$params = session_get_cookie_params();
|
| 284 |
$session_id = md5(uniqid(mt_rand(), TRUE));
|
| 285 |
setcookie($insecure_session_name, $session_id, REQUEST_TIME + $params['lifetime'], $params['path'], $params['domain'], FALSE, $params['httponly']);
|
| 286 |
$_COOKIE[$insecure_session_name] = $session_id;
|
| 287 |
}
|
| 288 |
|
| 289 |
if (drupal_session_started()) {
|
| 290 |
$old_session_id = session_id();
|
| 291 |
session_regenerate_id();
|
| 292 |
}
|
| 293 |
else {
|
| 294 |
// Start the session when it doesn't exist yet.
|
| 295 |
// Preserve the logged in user, as it will be reset to anonymous
|
| 296 |
// by _drupal_session_read.
|
| 297 |
$account = $user;
|
| 298 |
drupal_session_start();
|
| 299 |
$user = $account;
|
| 300 |
}
|
| 301 |
|
| 302 |
if (isset($old_session_id)) {
|
| 303 |
db_update('sessions')
|
| 304 |
->fields(array(
|
| 305 |
$is_https ? 'ssid' : 'sid' => session_id()
|
| 306 |
))
|
| 307 |
->condition('sid', $old_session_id)
|
| 308 |
->execute();
|
| 309 |
}
|
| 310 |
}
|
| 311 |
|
| 312 |
/**
|
| 313 |
* Counts how many users are active on the site.
|
| 314 |
*
|
| 315 |
* Counts how many users have sessions which have been active since the
|
| 316 |
* specified time. Can count either anonymous sessions or
|
| 317 |
* authenticated sessions.
|
| 318 |
*
|
| 319 |
* @param int $timestamp.
|
| 320 |
* A Unix timestamp. Users who have been active since this time will be
|
| 321 |
* counted. The default is 0, which counts all existing sessions.
|
| 322 |
* @param boolean $anonymous
|
| 323 |
* TRUE counts only anonymous users.
|
| 324 |
* FALSE counts only authenticated users.
|
| 325 |
* @return int
|
| 326 |
* The number of users with sessions.
|
| 327 |
*/
|
| 328 |
function drupal_session_count($timestamp = 0, $anonymous = TRUE) {
|
| 329 |
$query = db_select('sessions');
|
| 330 |
$query->addExpression('COUNT(sid)', 'count');
|
| 331 |
$query->condition('timestamp', $timestamp, '>=');
|
| 332 |
$query->condition('uid', 0, $anonymous ? '=' : '>');
|
| 333 |
return $query->execute()->fetchField();
|
| 334 |
}
|
| 335 |
|
| 336 |
/**
|
| 337 |
* Session handler assigned by session_set_save_handler().
|
| 338 |
*
|
| 339 |
* Cleanup a specific session.
|
| 340 |
*
|
| 341 |
* @param string $sid
|
| 342 |
* Session ID.
|
| 343 |
*/
|
| 344 |
function _drupal_session_destroy($sid) {
|
| 345 |
global $user, $is_https;
|
| 346 |
|
| 347 |
// Delete session data.
|
| 348 |
db_delete('sessions')
|
| 349 |
->condition($is_https ? 'ssid' : 'sid', $sid)
|
| 350 |
->execute();
|
| 351 |
|
| 352 |
// Reset $_SESSION and $user to prevent a new session from being started
|
| 353 |
// in drupal_session_commit().
|
| 354 |
$_SESSION = array();
|
| 355 |
$user = drupal_anonymous_user();
|
| 356 |
|
| 357 |
// Unset the session cookies.
|
| 358 |
_drupal_session_delete_cookie(session_name());
|
| 359 |
if ($is_https) {
|
| 360 |
_drupal_session_delete_cookie(substr(session_name(), 1), TRUE);
|
| 361 |
}
|
| 362 |
}
|
| 363 |
|
| 364 |
/**
|
| 365 |
* Deletes the session cookie.
|
| 366 |
*
|
| 367 |
* @param $name
|
| 368 |
* Name of session cookie to delete.
|
| 369 |
* @param $force_insecure
|
| 370 |
* Fornce cookie to be insecure.
|
| 371 |
*/
|
| 372 |
function _drupal_session_delete_cookie($name, $force_insecure = FALSE) {
|
| 373 |
if (isset($_COOKIE[$name])) {
|
| 374 |
$params = session_get_cookie_params();
|
| 375 |
setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']);
|
| 376 |
unset($_COOKIE[$name]);
|
| 377 |
}
|
| 378 |
}
|
| 379 |
|
| 380 |
/**
|
| 381 |
* End a specific user's session(s).
|
| 382 |
*
|
| 383 |
* @param string $uid
|
| 384 |
* User ID.
|
| 385 |
*/
|
| 386 |
function drupal_session_destroy_uid($uid) {
|
| 387 |
db_delete('sessions')
|
| 388 |
->condition('uid', $uid)
|
| 389 |
->execute();
|
| 390 |
}
|
| 391 |
|
| 392 |
/**
|
| 393 |
* Session handler assigned by session_set_save_handler().
|
| 394 |
*
|
| 395 |
* Cleanup stalled sessions.
|
| 396 |
*
|
| 397 |
* @param int $lifetime
|
| 398 |
* The value of session.gc_maxlifetime, passed by PHP.
|
| 399 |
* Sessions not updated for more than $lifetime seconds will be removed.
|
| 400 |
*/
|
| 401 |
function _drupal_session_garbage_collection($lifetime) {
|
| 402 |
// Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
|
| 403 |
// value. For example, if you want user sessions to stay in your database
|
| 404 |
// for three weeks before deleting them, you need to set gc_maxlifetime
|
| 405 |
// to '1814400'. At that value, only after a user doesn't log in after
|
| 406 |
// three weeks (1814400 seconds) will his/her session be removed.
|
| 407 |
db_delete('sessions')
|
| 408 |
->condition('timestamp', REQUEST_TIME - $lifetime, '<')
|
| 409 |
->execute();
|
| 410 |
return TRUE;
|
| 411 |
}
|
| 412 |
|
| 413 |
/**
|
| 414 |
* Determine whether to save session data of the current request.
|
| 415 |
*
|
| 416 |
* This function allows the caller to temporarily disable writing of
|
| 417 |
* session data, should the request end while performing potentially
|
| 418 |
* dangerous operations, such as manipulating the global $user object.
|
| 419 |
* See http://drupal.org/node/218104 for usage.
|
| 420 |
*
|
| 421 |
* @param $status
|
| 422 |
* Disables writing of session data when FALSE, (re-)enables
|
| 423 |
* writing when TRUE.
|
| 424 |
* @return
|
| 425 |
* FALSE if writing session data has been disabled. Otherwise, TRUE.
|
| 426 |
*/
|
| 427 |
function drupal_save_session($status = NULL) {
|
| 428 |
$save_session = &drupal_static(__FUNCTION__, TRUE);
|
| 429 |
if (isset($status)) {
|
| 430 |
$save_session = $status;
|
| 431 |
}
|
| 432 |
return $save_session;
|
| 433 |
}
|