Refactor session handling by wrapping all session handling function in a DrupalSession object, and publishing it as $_SESSION. From: Damien Tournoud --- includes/bootstrap.inc | 12 - includes/cache.inc | 4 includes/common.inc | 9 includes/file.inc | 8 includes/form.inc | 6 includes/language.inc | 2 includes/locale.inc | 2 includes/session.inc | 580 ++++++++++++++++---------- includes/theme.inc | 7 install.php | 2 modules/block/block.module | 6 modules/blog/blog.module | 2 modules/blog/blog.pages.inc | 4 modules/blogapi/blogapi.module | 2 modules/comment/comment.module | 20 - modules/comment/comment.pages.inc | 2 modules/contact/contact.module | 2 modules/contact/contact.pages.inc | 11 modules/dblog/dblog.admin.inc | 5 modules/filter/filter.module | 2 modules/forum/forum.module | 11 modules/node/node.module | 14 - modules/node/node.pages.inc | 8 modules/php/php.module | 2 modules/poll/poll.module | 10 modules/profile/profile.module | 2 modules/simpletest/drupal_web_test_case.php | 2 modules/simpletest/tests/file.test | 10 modules/simpletest/tests/session.test | 10 modules/simpletest/tests/session_test.module | 2 modules/statistics/statistics.module | 2 modules/system/system.api.php | 2 modules/system/system.module | 10 modules/tracker/tracker.module | 2 modules/trigger/trigger.test | 2 modules/upload/upload.module | 4 modules/user/user.module | 91 +--- modules/user/user.pages.inc | 27 - 38 files changed, 504 insertions(+), 395 deletions(-) diff --git includes/bootstrap.inc includes/bootstrap.inc index 7e49d32..46ac46b 100644 --- includes/bootstrap.inc +++ includes/bootstrap.inc @@ -658,7 +658,8 @@ function variable_del($name) { * a redirected form submission which was completed). */ function page_get_cache() { - global $user, $base_root; + global $base_root; + $user = $_SESSION->user; $cache = NULL; @@ -883,7 +884,8 @@ function request_uri() { * @see watchdog_severity_levels() */ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { - global $user, $base_root; + global $base_root; + $user = $_SESSION->user; static $in_error_state = FALSE; @@ -1120,8 +1122,7 @@ function _drupal_bootstrap($phase) { case DRUPAL_BOOTSTRAP_SESSION: require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc'); - session_set_save_handler('_sess_open', '_sess_close', '_sess_read', '_sess_write', '_sess_destroy_sid', '_sess_gc'); - session_start(); + DrupalSession::init(); break; case DRUPAL_BOOTSTRAP_VARIABLES: @@ -1199,7 +1200,8 @@ function get_t() { * Choose a language for the current page, based on site and user preferences. */ function drupal_init_language() { - global $language, $user; + global $language; + $user = $_SESSION->user; // Ensure the language is correctly returned, even without multilanguage support. // Useful for eg. XML/HTML 'lang' attributes. diff --git includes/cache.inc includes/cache.inc index 108bd2a..8c82ede 100644 --- includes/cache.inc +++ includes/cache.inc @@ -15,7 +15,7 @@ * @return The cache or FALSE on failure. */ function cache_get($cid, $table = 'cache') { - global $user; + $user = $_SESSION->user; // Garbage collection necessary when enforcing a minimum cache lifetime $cache_flush = variable_get('cache_flush', 0); @@ -145,7 +145,7 @@ function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $he * match. If '*' is given as $cid, the table $table will be emptied. */ function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) { - global $user; + $user = $_SESSION->user; if (!isset($cid) && !isset($table)) { // Clear the block cache first, so stale data will diff --git includes/common.inc includes/common.inc index b9efee2..3f8a96e 100644 --- includes/common.inc +++ includes/common.inc @@ -341,7 +341,7 @@ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response // Even though session_write_close() is registered as a shutdown function, we // need all session data written to the database before redirecting. - session_write_close(); + $_SESSION->close(); header('Location: ' . $url, TRUE, $http_response_code); @@ -1462,7 +1462,7 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) { function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { static $timezones = array(); if (!isset($timezone)) { - global $user; + $user = $_SESSION->user; if (variable_get('configurable_timezones', 1) && $user->uid && $user->timezone) { $timezone = $user->timezone; } @@ -2829,7 +2829,7 @@ function drupal_get_token($value = '') { * is true, the return value will always be true for anonymous users. */ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { - global $user; + $user = $_SESSION->user; return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', '')))); } @@ -2912,7 +2912,8 @@ function _drupal_bootstrap_full() { * @see drupal_page_header */ function page_set_cache() { - global $user, $base_root; + global $base_root; + $user = $_SESSION->user; if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_get_messages(NULL, FALSE)) == 0) { // This will fail in some cases, see page_get_cache() for the explanation. diff --git includes/file.inc includes/file.inc index a69d9c6..0e68e15 100644 --- includes/file.inc +++ includes/file.inc @@ -800,7 +800,7 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { * error. */ function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) { - global $user; + $user = $_SESSION->user; static $upload_cache; // Return cached objects without processing since the file will have @@ -974,7 +974,7 @@ function file_validate_name_length($file) { * @see hook_file_validate() */ function file_validate_extensions($file, $extensions) { - global $user; + $user = $_SESSION->user; $errors = array(); @@ -1005,7 +1005,7 @@ function file_validate_extensions($file, $extensions) { * @see hook_file_validate() */ function file_validate_size($file, $file_limit = 0, $user_limit = 0) { - global $user; + $user = $_SESSION->user; $errors = array(); @@ -1124,7 +1124,7 @@ function file_validate_image_resolution(&$file, $maximum_dimensions = 0, $minimu * @see file_unmanaged_save_data() */ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - global $user; + $user = $_SESSION->user; if ($filepath = file_unmanaged_save_data($data, $destination, $replace)) { // Create a file object. diff --git includes/form.inc includes/form.inc index 2de5fc7..f48b3f8 100644 --- includes/form.inc +++ includes/form.inc @@ -221,7 +221,7 @@ function drupal_rebuild_form($form_id, &$form_state, $args, $form_build_id = NUL function form_get_cache($form_build_id, &$form_state) { if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) { $form = $cached->data; - global $user; + $user = $_SESSION->user; if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) { if ($cached = cache_get('storage_' . $form_build_id, 'cache_form')) { $form_state['storage'] = $cached->data; @@ -237,7 +237,7 @@ function form_get_cache($form_build_id, &$form_state) { function form_set_cache($form_build_id, $form, $form_state) { // 6 hours cache life time for forms should be plenty. $expire = 21600; - global $user; + $user = $_SESSION->user; if ($user->uid) { $form['#cache_token'] = drupal_get_token(); } @@ -462,7 +462,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { * in here so that hook_form_alter() calls can use it, as well. */ function drupal_prepare_form($form_id, &$form, &$form_state) { - global $user; + $user = $_SESSION->user; $form['#type'] = 'form'; $form['#programmed'] = isset($form['#post']); diff --git includes/language.inc includes/language.inc index 9494683..c45c5a9 100644 --- includes/language.inc +++ includes/language.inc @@ -10,7 +10,7 @@ * Choose a language for the page, based on language negotiation settings. */ function language_initialize() { - global $user; + $user = $_SESSION->user; // Configured presentation language mode. $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE); diff --git includes/locale.inc includes/locale.inc index d019836..c361fff 100644 --- includes/locale.inc +++ includes/locale.inc @@ -1751,7 +1751,7 @@ function _locale_export_get_strings($language = NULL, $group = 'default') { * are provided for PO and POT files. */ function _locale_export_po_generate($language = NULL, $strings = array(), $header = NULL) { - global $user; + $user = $_SESSION->user; if (!isset($header)) { if (isset($language)) { diff --git includes/session.inc includes/session.inc index 4f65b7f..a9f0445 100644 --- includes/session.inc +++ includes/session.inc @@ -3,176 +3,393 @@ /** * @file - * User session handling functions. - * - * The user-level session storage handlers: - * - _sess_open() - * - _sess_close() - * - _sess_read() - * - _sess_write() - * - _sess_destroy_sid() - * - _sess_gc() - * are assigned by session_set_save_handler() in bootstrap.inc and are called - * automatically by PHP. These functions should not be called directly. Session - * data should instead be accessed via the $_SESSION superglobal. + * Session handling features. */ /** - * Session handler assigned by session_set_save_handler(). - * - * This function is used to handle any initialization, such as file paths or - * database connections, that is needed before accessing session data. Drupal - * does not need to initialize anything in this function. - * - * This function should not be called directly. - * - * @return - * This function will always return TRUE. + * Provides session handling and validation. */ -function _sess_open() { - return TRUE; -} +class DrupalSession extends ArrayIterator { + /** + * The original uid of the current user, as retrieved and set in + * Session::read(), is stored in this private member so that we can verify + * against it during session writing and cleanup. + * + * @var int + */ + private $uid; -/** - * Session handler assigned by session_set_save_handler(). - * - * This function is used to close the current session. Because Drupal stores - * session data in the database immediately on write, this function does - * not need to do anything. - * - * This function should not be called directly. - * - * @return - * This function will always return TRUE. - */ -function _sess_close() { - return TRUE; -} + /** + * The currently logged in user. + * + * The currently logged in user, previously the $user = $_SESSION->user, is now attached + * directly to the global session object. + * + * @var stdClass + */ + public $user; -/** - * Session handler assigned by session_set_save_handler(). - * - * This function will be called by PHP to retrieve the current user's - * session data, which is stored in the database. It also loads the - * current user's appropriate roles into the user object. - * - * This function should not be called directly. Session data should - * instead be accessed via the $_SESSION superglobal. - * - * @param $key - * Session ID. - * @return - * Either an array of the session data, or an empty string, if no data - * was found or the user is anonymous. - */ -function _sess_read($key) { - global $user; - - // Write and Close handlers are called after destructing objects - // since PHP 5.0.5. - // Thus destructors can use sessions but session handler can't use objects. - // So we are moving session closure before destructing objects. - register_shutdown_function('session_write_close'); - - // Handle the case of first time visitors and clients that don't store - // cookies (eg. web crawlers). - if (!isset($_COOKIE[session_name()])) { - $user = drupal_anonymous_user(); - return ''; + /** + * Prevent writing the session (useful for test purposes). + * + * @var boolean + */ + public $preventWrite = FALSE; + + /** + * Build the session object. + * + * The user-level session storage handlers: + * - DrupalSession::open() + * - DrupalSession::close() + * - DrupalSession::read() + * - DrupalSession::write() + * - DrupalSession::destroy() + * - DrupalSession::gc() + * are assigned by session_set_save_handler() in bootstrap.inc and are called + * automatically by PHP. These functions should not be called directly. Session + * data should instead be accessed via the $_SESSION superglobal. + */ + public function __construct() { + session_set_save_handler( + array(&$this, "openHandler"), + array(&$this, "closeHandler"), + array(&$this, "readHandler"), + array(&$this, "writeHandler"), + array(&$this, "destroyHandler"), + array(&$this, "gcHandler") + ); + session_start(); + parent::__construct($_SESSION); + } + + /** + * Initialize session handling. + * + * We wrap $_SESSION to our DrupalSession object, so that we can control + * what happens during the lifetime of the session. + */ + public static function init() { + $_SESSION = new DrupalSession(); } - // Otherwise, if the session is still active, we have a record of the - // client's session in the database. - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $key))->fetchObject(); + /** + * Finalize the login process. Must be called when logging in a user. + * + * The method checks the account object that is to replace the current user, + * records a watchdog message about the new session, saves the login + * timestamp, calls hook_user op 'login' and generates a new session. + * + * @param $account + * The new user object intended to replace the currently logged in user. + * @param $edit + * This array is passed to hook_user op login. + * @return + * Returns the new current user object. + */ + public function userLogin($account, &$edit) { + // Set the provided account as the new current user. + $this->user = $account; + // Set the protected uid record to the new authorized uid. + $this->uid = $this->user->uid; + + watchdog('user', 'Session opened for %name.', array('%name' => $this->user->name)); + // Update the user table timestamp noting user has logged in. + // This is also used to invalidate one-time login links. + $this->user->login = REQUEST_TIME; + db_update('users') + ->fields(array( + 'login' => $this->user->login + )) + ->condition('uid', $this->user->uid) + ->execute(); - // We found the client's session record and they are an authenticated user. - if ($user && $user->uid > 0) { - // This is done to unserialize the data member of $user. - $user = drupal_unpack($user); + // Regenerate the session ID to prevent against session fixation attacks. + // This is called before hook_user in case one of those functions fails + // or incorrectly does a redirect which would leave the old session in place. + $this->regenerate(); - // Add roles element to $user. - $user->roles = array(); - $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - $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); + user_module_invoke('login', $edit, $this->user); + return $this->user; } - // We didn't find the client's record (session has expired), or they - // are an anonymous user. - else { - $session = isset($user->session) ? $user->session : ''; - $user = drupal_anonymous_user($session); + + /** + * Logout the current user. + */ + public function userLogout() { + watchdog('user', 'Session closed for %name.', array('%name' => $this->user->name)); + // Destroy the current user's session. + session_destroy(); + $this->regenerate(); + + module_invoke_all('user_logout', NULL, $this->user); + // Load the anonymous user into the current user. + $this->user = drupal_anonymous_user(); + $this->uid = 0; } - return $user->session; -} + /** + * Remove all sessions associated with the given uid from the sessions table. + * + * Note that this method does NOT affect the session associated with the + * current page request, as it does not invoke any of PHP's native session + * handling functions. Even though the session data is deleted here, it will + * be restored at the end of the page request in Session::write(). + * Consequently, there are two major use cases for calling this method: + * - A user has changed their password, and so all saved sessiosn should be + * destroyed for security reasons. @see user_save() + * - A user is being deleted. @see user_delete() + * - A user has been blocked, and so all saved sessions should be destroyed + * to ensure that they cannot resume a saved session when they later return + * to the site, and that the block is immediately effective (i.e., if they + * are currently browsing, will take effect as of the next page request + * they make). @see user_block_user_action() + * + * @param int $uid + * The user's uid whose sessions are to be removed. + * @return bool + * Returns TRUE to indicate success; always returns this since there are no + * conditions under which it should fail. + */ + public static function destroyByUid($uid) { + db_delete('sessions') + ->condition('uid', $uid) + ->execute(); + return TRUE; + } -/** - * Session handler assigned by session_set_save_handler(). - * - * This function will be called by PHP to store the current user's - * session, which Drupal saves to the database. - * - * This function should not be called directly. Session data should - * instead be accessed via the $_SESSION superglobal. - * - * @param $key - * Session ID. - * @param $value - * Serialized array of the session data. - * @return - * This function will always return TRUE. - */ -function _sess_write($key, $value) { - global $user; - - // If saving of session data is disabled or if the client doesn't have a session, - // and one isn't being created ($value), do nothing. This keeps crawlers out of - // the session table. This reduces memory and server load, and gives more useful - // statistics. We can't eliminate anonymous session table rows without breaking - // the "Who's Online" block. - if (!drupal_save_session() || ($user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) { + /** + * Close the current session. + * + * @return bool + * Indicates whether or not the session was successfully closed. The only + * time this should be false is if the function is called when + */ + public function close() { + $_SESSION = $this->getArrayCopy(); + session_write_close(); + $_SESSION = $this; return TRUE; } - db_merge('sessions') - ->key(array('sid' => $key)) - ->fields(array( - 'uid' => $user->uid, - 'cache' => isset($user->cache) ? $user->cache : 0, - 'hostname' => ip_address(), - 'session' => $value, - 'timestamp' => REQUEST_TIME, - )) - ->execute(); - - // Last access time is updated no more frequently than once every 180 seconds. - // This reduces contention in the users table. - if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) { - db_update('users') + /** + * Regenerate the current session. + */ + public function regenerate() { + $old_session_id = session_id(); + // This will define $lifetime, $path, $domain, $secure used below. + extract(session_get_cookie_params()); + // Set "httponly" to TRUE to reduce the risk of session stealing via XSS. + session_set_cookie_params($lifetime, $path, $domain, $secure, TRUE); + session_regenerate_id(); + db_update('sessions') ->fields(array( - 'access' => REQUEST_TIME + 'sid' => session_id() )) - ->condition('uid', $user->uid) + ->condition('sid', $old_session_id) ->execute(); + return TRUE; } - return TRUE; -} + /** + * 'Open' session handler assigned by session_set_save_handler(). + * + * This method is used to handle any initialization, such as file paths or + * database connections, that is needed before accessing session data. Drupal + * does not need to initialize anything in this function. + * + * @return + * This method will always return TRUE. + */ + protected function openHandler() { + return TRUE; + } -/** - * Called when an anonymous user becomes authenticated or vice-versa. - */ -function drupal_session_regenerate() { - $old_session_id = session_id(); - extract(session_get_cookie_params()); - // Set "httponly" to TRUE to reduce the risk of session stealing via XSS. - session_set_cookie_params($lifetime, $path, $domain, $secure, TRUE); - session_regenerate_id(); - db_update('sessions') - ->fields(array( - 'sid' => session_id() - )) - ->condition('sid', $old_session_id) - ->execute(); + /** + * 'Close' session handler assigned by session_set_save_handler(). + * + * This method is used to close the current session. Because Drupal stores + * session data in the database immediately on write, this function does + * not need to do anything. + * + * @return + * This method will always return TRUE. + */ + protected function closeHandler() { + return TRUE; + } + + /** + * 'Read' session handler assigned by session_set_save_handler(). + * + * This method will be called internally by PHP to retrieve the current user's + * session data, which is stored in the database. It also loads the + * current user's appropriate roles into the user object. + * + * @param string $key + * Session ID; exactly the same string value as what session_id() returns. + * @return mixed + * If no session data was found or if the user is anonymous, an empty string + * is returned. Otherwise, a special serialized string is returned that the + * PHP engine interprets into our $_SESSION array. The form of the string is + * governed by the session.serialize_handler variable in php.ini. + * + */ + protected function readHandler($sid) { + // Handle clients who present no cookies. These could be first-time visitors, + // regular visitors whose session data has just been purged (e.g., by + // logging out), or clients that don't store cookies (e.g., web crawlers). + if (!isset($_COOKIE[session_name()])) { + $this->user = drupal_anonymous_user(); + return ''; + } + + // Otherwise, if the session is still active, we have a record of the + // client's session in the database. + $this->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(); + + // We found the client's session record and they are an authenticated user. + if ($this->user && $this->user->uid > 0) { + // This is done to unserialize the data member of $user. + $this->user = drupal_unpack($this->user); + + // Add roles element to $this->user. + $this->user->roles = array(); + $this->user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + $this->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' => $this->user->uid))->fetchAllKeyed(0, 1); + } + // We didn't find the client's record (session has expired), or they + // are an anonymous user. + else { + $session = isset($this->user->session) ? $this->user->session : ''; + $this->user = drupal_anonymous_user($session); + } + + // Store the current user's uid in a protected property for verification + // against later. + $this->uid = $this->user->uid; + + return $this->user->session; + } + + /** + * 'Write' session handler assigned by session_set_save_handler(). + * + * This method will be called by PHP to store the current user's + * session, which Drupal saves to the database. + * + * @param $sid + * Session ID. + * @param $value + * Serialized array of the session data. + * @return + * This function will always return TRUE. + */ + public function writeHandler($sid, $value) { + // Ensure we are safe to proceed with writing session data. + if (!$this->writeCheck($value)) { + return FALSE; + } + + db_merge('sessions') + ->key(array('sid' => $sid)) + ->fields(array( + 'uid' => $this->user->uid, + 'cache' => isset($this->user->cache) ? $this->user->cache : 0, + 'hostname' => ip_address(), + 'session' => $value, + 'timestamp' => REQUEST_TIME, + )) + ->execute(); + + // Last access time is updated no more frequently than once every 180 seconds. + // This reduces contention in the users table. + if ($this->user->uid && REQUEST_TIME - $this->user->access > variable_get('session_write_interval', 180)) { + db_update('users') + ->fields(array( + 'access' => REQUEST_TIME + )) + ->condition('uid', $this->user->uid) + ->execute(); + } + + return TRUE; + } + + /** + * Perform validation and security checks to ensure it is safe and appropriate + * to write data to the session table. + * + * First, if there is a mismatch between the user uid that was originally + * loaded into session data and Session::user->uid, then something has gone + * wrong, so we bail out without updating session data. + * + * Then, if the client doesn't have a session, and one isn't being created + * ($value), do nothing. This keeps crawlers out of the session table, which + * reduces memory and server load, and results in more useful statistics. We + * can't eliminate anonymous session table rows without breaking the + * "Who's Online" block. + * + * @param $value + * Serialized array containing session data. + * @return bool + * Returns a boolean indicating whether the session write should continue. + */ + protected function writeCheck($value) { + if ($this->preventWrite || $this->uid != $this->user->uid || + ($this->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) { + return FALSE; + } + return TRUE; + } + + /** + * 'Destroy' session handler assigned by session_set_save_handler(). + * + * Cleanup a specific session. + * + * @param string $sid + * Session ID. + */ + protected function destroyHandler($sid) { + db_delete('sessions') + ->condition('sid', $sid) + ->execute(); + } + + /** + * Session handler assigned by session_set_save_handler(). + * + * Cleanup stalled sessions. + * + * @param int $lifetime + * The value of session.gc_maxlifetime, passed by PHP. + * Sessions not updated for more than $lifetime seconds will be removed. + */ + private function gcHandler($lifetime) { + // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough + // value. For example, if you want user sessions to stay in your database + // for three weeks before deleting them, you need to set gc_maxlifetime + // to '1814400'. At that value, only after a user doesn't log in after + // three weeks (1814400 seconds) will his/her session be removed. + db_delete('sessions') + ->condition('timestamp', REQUEST_TIME - $lifetime, '<') + ->execute(); + return TRUE; + } + + /** + * Destructor magic method, called internally during PHP's shutdown sequence. + * + * By calling session_write_close() here via Session::sessionClose(), we are + * able to circumvent PHP's native shutdown ordering, which destructs objects + * before calling session Write and Close handlers. + * + * @return void + */ + public function __destruct() { + $this->close(); + } } /** @@ -198,72 +415,3 @@ function drupal_session_count($timestamp = 0, $anonymous = TRUE) { $query->condition('uid', 0, $anonymous ? '=' : '>'); return $query->execute()->fetchField(); } - -/** - * Session handler assigned by session_set_save_handler(). - * - * Cleanup a specific session. - * - * @param string $sid - * Session ID. - */ -function _sess_destroy_sid($sid) { - db_delete('sessions') - ->condition('sid', $sid) - ->execute(); -} - -/** - * End a specific user's session(s). - * - * @param string $uid - * User ID. - */ -function drupal_session_destroy_uid($uid) { - db_delete('sessions') - ->condition('uid', $uid) - ->execute(); -} - -/** - * Session handler assigned by session_set_save_handler(). - * - * Cleanup stalled sessions. - * - * @param int $lifetime - * The value of session.gc_maxlifetime, passed by PHP. - * Sessions not updated for more than $lifetime seconds will be removed. - */ -function _sess_gc($lifetime) { - // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough - // value. For example, if you want user sessions to stay in your database - // for three weeks before deleting them, you need to set gc_maxlifetime - // to '1814400'. At that value, only after a user doesn't log in after - // three weeks (1814400 seconds) will his/her session be removed. - db_delete('sessions') - ->condition('timestamp', REQUEST_TIME - $lifetime, '<') - ->execute(); - return TRUE; -} - -/** - * Determine whether to save session data of the current request. - * - * This function allows the caller to temporarily disable writing of - * session data, should the request end while performing potentially - * dangerous operations, such as manipulating the global $user object. - * See http://drupal.org/node/218104 for usage. - * - * @param $status - * Disables writing of session data when FALSE, (re-)enables - * writing when TRUE. - * @return - * FALSE if writing session data has been disabled. Otherwise, TRUE. - */ -function drupal_save_session($status = NULL) { - static $save_session = TRUE; - if (isset($status)) { - $save_session = $status; - } - return $save_session; -} diff --git includes/theme.inc includes/theme.inc index e0b3cbe..c985272 100644 --- includes/theme.inc +++ includes/theme.inc @@ -41,7 +41,8 @@ define('MARK_UPDATED', 2); * Initialize the theme system by loading the theme. */ function init_theme() { - global $theme, $user, $custom_theme, $theme_key; + global $theme, $custom_theme, $theme_key; + $user = $_SESSION->user; // If $theme is already set, assume the others are set, too, and do nothing if (isset($theme)) { @@ -1484,7 +1485,7 @@ function theme_box($title, $content, $region = 'main') { * A string containing the marker. */ function theme_mark($type = MARK_NEW) { - global $user; + $user = $_SESSION->user; if ($user->uid) { if ($type == MARK_NEW) { return ' ' . t('new') . ''; @@ -1763,7 +1764,7 @@ function _theme_table_cell($cell, $header = FALSE) { * theme functions). */ function template_preprocess(&$variables, $hook) { - global $user; + $user = $_SESSION->user; static $count = array(); // Track run count for each hook to provide zebra striping. diff --git install.php install.php index 8679616..48049e6 100644 --- install.php +++ install.php @@ -1147,7 +1147,7 @@ function install_configure_form_validate($form, &$form_state) { * Form API submit for the site configuration form. */ function install_configure_form_submit($form, &$form_state) { - global $user; + $user = $_SESSION->user; variable_set('site_name', $form_state['values']['site_name']); variable_set('site_mail', $form_state['values']['site_mail']); diff --git modules/block/block.module modules/block/block.module index dcdc0dd..7d2fdd6 100644 --- modules/block/block.module +++ modules/block/block.module @@ -409,7 +409,8 @@ function block_list($region) { * Load blocks information from the database. */ function _block_load_blocks() { - global $user, $theme_key; + global $theme_key; + $user = $_SESSION->user; $blocks = array(); $rids = array_keys($user->roles); @@ -527,7 +528,8 @@ function _block_render_blocks($region_blocks) { * The string used as cache_id for the block. */ function _block_get_cache_id($block) { - global $theme, $base_root, $user; + global $theme, $base_root; + $user = $_SESSION->user; // User 1 being out of the regular 'roles define permissions' schema, // it brings too many chances of having unwanted output get in the cache diff --git modules/blog/blog.module modules/blog/blog.module index 6b1c79f..b4dd403 100644 --- modules/blog/blog.module +++ modules/blog/blog.module @@ -170,7 +170,7 @@ function _blog_post_exists($account) { * Displays the most recent 10 blog titles. */ function blog_block($op = 'list', $delta = '') { - global $user; + $user = $_SESSION->user; if ($op == 'list') { $block['recent']['info'] = t('Recent blog posts'); return $block; diff --git modules/blog/blog.pages.inc modules/blog/blog.pages.inc index 57b5746..9178a50 100644 --- modules/blog/blog.pages.inc +++ modules/blog/blog.pages.inc @@ -10,7 +10,7 @@ * Menu callback; displays a Drupal page containing recent blog entries of a given user. */ function blog_page_user($account) { - global $user; + $user = $_SESSION->user; drupal_set_title($title = t("@name's blog", array('@name' => $account->name)), PASS_THROUGH); @@ -53,7 +53,7 @@ function blog_page_user($account) { * Menu callback; displays a Drupal page containing recent blog entries of all users. */ function blog_page_last() { - global $user; + $user = $_SESSION->user; $output = ''; $items = array(); diff --git modules/blogapi/blogapi.module modules/blogapi/blogapi.module index 1b51981..4e464d9 100644 --- modules/blogapi/blogapi.module +++ modules/blogapi/blogapi.module @@ -683,7 +683,7 @@ function blogapi_error($message) { * Ensure that the given user has permission to edit a blog. */ function blogapi_validate_user($username, $password) { - global $user; + $user = $_SESSION->user; $user = user_authenticate(array('name' => $username, 'pass' => $password)); diff --git modules/comment/comment.module modules/comment/comment.module index 53092ed..94fba7d 100644 --- modules/comment/comment.module +++ modules/comment/comment.module @@ -704,7 +704,7 @@ function comment_user_delete(&$edit, &$user, $category = NULL) { * TRUE if the current user has acces to the comment, FALSE otherwise. */ function comment_access($op, $comment) { - global $user; + $user = $_SESSION->user; if ($op == 'edit') { return ($user->uid && $user->uid == $comment->uid && comment_num_replies($comment->cid) == 0) || user_access('administer comments'); @@ -732,7 +732,7 @@ function comment_node_url() { * is not saved, FALSE is returned. */ function comment_save($edit) { - global $user; + $user = $_SESSION->user; if (user_access('post comments') && (user_access('administer comments') || node_comment_mode($edit['nid']) == COMMENT_NODE_READ_WRITE)) { if (!form_get_errors()) { $edit += array( @@ -875,7 +875,7 @@ function comment_save($edit) { * An associative array containing the links. */ function comment_links($comment, $return = 1) { - global $user; + $user = $_SESSION->user; $links = array(); // If viewing just this comment, link back to the node. @@ -992,7 +992,7 @@ function comment_links($comment, $return = 1) { * to consider the trailing "/" so we use a substring only. */ function comment_render($node, $cid = 0) { - global $user; + $user = $_SESSION->user; $output = ''; if (user_access('access comments')) { @@ -1192,7 +1192,7 @@ function comment_num_replies($pid) { * @return The result or FALSE on error. */ function comment_num_new($nid, $timestamp = 0) { - global $user; + $user = $_SESSION->user; if ($user->uid) { // Retrieve the timestamp at which the current user last viewed this node. @@ -1223,7 +1223,7 @@ function comment_num_new($nid, $timestamp = 0) { * The original $edit. */ function comment_validate($edit) { - global $user; + $user = $_SESSION->user; // Invoke other validation handlers. comment_invoke_comment($edit, 'validate'); @@ -1286,7 +1286,7 @@ function comment_validate($edit) { * @see comment_form_submit() */ function comment_form(&$form_state, $edit, $title = NULL) { - global $user; + $user = $_SESSION->user; $op = isset($_POST['op']) ? $_POST['op'] : ''; $node = node_load($edit['nid']); @@ -1539,7 +1539,7 @@ function comment_form_box($edit, $title = NULL) { * @ingroup forms */ function comment_form_add_preview($form, &$form_state) { - global $user; + $user = $_SESSION->user; $edit = $form_state['values']; drupal_set_title(t('Preview comment'), PASS_THROUGH); $output = ''; @@ -1611,7 +1611,7 @@ function comment_form_add_preview($form, &$form_state) { * Validate comment form submissions. */ function comment_form_validate($form, &$form_state) { - global $user; + $user = $_SESSION->user; if ($user->uid === 0) { foreach (array('name', 'homepage', 'mail') as $field) { // Set cookie for 365 days. @@ -1814,7 +1814,7 @@ function theme_comment_thread_expanded($comment, $node) { * @ingroup themeable */ function theme_comment_post_forbidden($node) { - global $user; + $user = $_SESSION->user; static $authenticated_post_comments; if (!$user->uid) { diff --git modules/comment/comment.pages.inc modules/comment/comment.pages.inc index 64ffd50..be6ac54 100644 --- modules/comment/comment.pages.inc +++ modules/comment/comment.pages.inc @@ -14,7 +14,7 @@ * @ingroup forms */ function comment_edit($cid) { - global $user; + $user = $_SESSION->user; $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid', array(':cid'=>$cid) )->fetchObject(); $comment = drupal_unpack($comment); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; diff --git modules/contact/contact.module modules/contact/contact.module index 34cc1f3..4c3b037 100644 --- modules/contact/contact.module +++ modules/contact/contact.module @@ -115,7 +115,7 @@ function contact_menu() { * Determine if a user can access to the contact tab. */ function _contact_user_tab_access($account) { - global $user; + $user = $_SESSION->user; if (!isset($account->contact)) { $account->contact = FALSE; } diff --git modules/contact/contact.pages.inc modules/contact/contact.pages.inc index 0e7d799..a6eac81 100644 --- modules/contact/contact.pages.inc +++ modules/contact/contact.pages.inc @@ -11,7 +11,7 @@ * Site-wide contact page. */ function contact_site_page() { - global $user; + $user = $_SESSION->user; if (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3)) && !user_access('administer site-wide contact form')) { $output = t("You cannot send more than %number messages per hour. Please try again later.", array('%number' => variable_get('contact_hourly_threshold', 3))); @@ -24,7 +24,7 @@ function contact_site_page() { } function contact_mail_page() { - global $user; + $user = $_SESSION->user; $form = $categories = array(); @@ -155,7 +155,7 @@ function contact_mail_page_submit($form, &$form_state) { * Personal contact page. */ function contact_user_page($account) { - global $user; + $user = $_SESSION->user; if (!valid_email_address($user->mail)) { $output = t('You need to provide a valid e-mail address to contact other users. Please update your user information and try again.', array('@url' => url("user/$user->uid/edit", array('query' => 'destination=' . drupal_get_destination())))); @@ -172,7 +172,7 @@ function contact_user_page($account) { } function contact_mail_user(&$form_state, $recipient) { - global $user; + $user = $_SESSION->user; $form['#token'] = $user->name . $user->mail; $form['recipient'] = array('#type' => 'value', '#value' => $recipient); $form['from'] = array('#type' => 'item', @@ -206,7 +206,8 @@ function contact_mail_user(&$form_state, $recipient) { * Process the personal contact page form submission. */ function contact_mail_user_submit($form, &$form_state) { - global $user, $language; + global $language; + $user = $_SESSION->user; $account = $form_state['values']['recipient']; diff --git modules/dblog/dblog.admin.inc modules/dblog/dblog.admin.inc index 20204ba..e01b8d1 100644 --- modules/dblog/dblog.admin.inc +++ modules/dblog/dblog.admin.inc @@ -261,8 +261,11 @@ function _dblog_format_message($dblog) { * @see dblog_filter_form_validate() */ function dblog_filter_form() { + if (empty($_SESSION['dblog_overview_filter'])) { + $_SESSION['dblog_overview_filter'] = array(); + } $session = &$_SESSION['dblog_overview_filter']; - $session = is_array($session) ? $session : array(); + $filters = dblog_filters(); $form['filters'] = array( diff --git modules/filter/filter.module modules/filter/filter.module index 9af5fef..eaa450a 100644 --- modules/filter/filter.module +++ modules/filter/filter.module @@ -286,7 +286,7 @@ function filter_filter_tips($delta, $format, $long = FALSE) { * Retrieve a list of input formats. */ function filter_formats($index = NULL) { - global $user; + $user = $_SESSION->user; static $formats; // Administrators can always use all input formats. diff --git modules/forum/forum.module modules/forum/forum.module index 11a7eac..0007d45 100644 --- modules/forum/forum.module +++ modules/forum/forum.module @@ -620,7 +620,8 @@ function _forum_topics_unread($term, $uid) { } function forum_get_topics($tid, $sortby, $forum_per_page) { - global $user, $forum_topic_list_header; + global $forum_topic_list_header; + $user = $_SESSION->user; $forum_topic_list_header = array( NULL, @@ -680,7 +681,7 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { * Finds the first unread node for a given forum. */ function _forum_new($tid) { - global $user; + $user = $_SESSION->user; $sql = "SELECT n.nid FROM {node} n LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND h.nid IS NULL AND n.created > %d ORDER BY created"; $sql = db_rewrite_sql($sql); @@ -703,7 +704,7 @@ function _forum_new($tid) { * @see forums.tpl.php */ function template_preprocess_forums(&$variables) { - global $user; + $user = $_SESSION->user; $vid = variable_get('forum_nav_vocabulary', ''); $vocabulary = taxonomy_vocabulary_load($vid); @@ -806,7 +807,7 @@ function template_preprocess_forums(&$variables) { * @see theme_forum_list() */ function template_preprocess_forum_list(&$variables) { - global $user; + $user = $_SESSION->user; $row = 0; // Sanitize each forum so that the template can safely print the data. foreach ($variables['forums'] as $id => $forum) { @@ -982,7 +983,7 @@ function template_preprocess_forum_submitted(&$variables) { } function _forum_user_last_visit($nid) { - global $user; + $user = $_SESSION->user; static $history = array(); if (empty($history)) { diff --git modules/node/node.module modules/node/node.module index 3cbd4b5..9d52cb0 100644 --- modules/node/node.module +++ modules/node/node.module @@ -187,7 +187,7 @@ function theme_node_list($items, $title = NULL) { * Update the 'last viewed' timestamp of the specified node for current user. */ function node_tag_new($nid) { - global $user; + $user = $_SESSION->user; if ($user->uid) { if (node_last_viewed($nid)) { @@ -204,7 +204,7 @@ function node_tag_new($nid) { * specified node. */ function node_last_viewed($nid) { - global $user; + $user = $_SESSION->user; static $history; if (!isset($history[$nid])) { @@ -225,7 +225,7 @@ function node_last_viewed($nid) { * One of the MARK constants. */ function node_mark($nid, $timestamp) { - global $user; + $user = $_SESSION->user; static $cache; if (!$user->uid) { @@ -945,7 +945,7 @@ function node_validate($node, $form = array()) { * Prepare node for save and allow modules to make changes. */ function node_submit($node) { - global $user; + $user = $_SESSION->user; // Convert the node to an object, if necessary. $node = (object)$node; @@ -989,7 +989,7 @@ function node_submit($node) { function node_save(&$node) { // Let modules modify the node before it is saved to the database. node_invoke_nodeapi($node, 'presave'); - global $user; + $user = $_SESSION->user; $node->is_new = FALSE; @@ -2165,7 +2165,7 @@ function node_search_validate($form, &$form_state) { * TRUE if the operation may be performed. */ function node_access($op, $node, $account = NULL) { - global $user; + $user = $_SESSION->user; if (!$node) { return FALSE; @@ -2301,7 +2301,7 @@ function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $accoun function node_access_grants($op, $account = NULL) { if (!isset($account)) { - $account = $GLOBALS['user']; + $account = &$_SESSION->user; } return array_merge(array('all' => array(0)), module_invoke_all('node_grants', $account, $op)); diff --git modules/node/node.pages.inc modules/node/node.pages.inc index 3e7411a..7214d05 100644 --- modules/node/node.pages.inc +++ modules/node/node.pages.inc @@ -50,7 +50,7 @@ function theme_node_add_list($content) { * Present a node submission form or a set of links to such forms. */ function node_add($type) { - global $user; + $user = $_SESSION->user; $types = node_get_types(); $type = isset($type) ? str_replace('-', '_', $type) : NULL; @@ -78,7 +78,7 @@ function node_object_prepare(&$node) { foreach (array('status', 'promote', 'sticky') as $key) { $node->$key = in_array($key, $node_options); } - global $user; + $user = $_SESSION->user; $node->uid = $user->uid; $node->created = REQUEST_TIME; } @@ -96,7 +96,7 @@ function node_object_prepare(&$node) { * Generate the node add/edit form array. */ function node_form(&$form_state, $node) { - global $user; + $user = $_SESSION->user; if (isset($form_state['node'])) { $node = $form_state['node'] + (array)$node; @@ -428,7 +428,7 @@ function theme_node_preview($node) { } function node_form_submit($form, &$form_state) { - global $user; + $user = $_SESSION->user; $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); diff --git modules/php/php.module modules/php/php.module index 7090239..52793d3 100644 --- modules/php/php.module +++ modules/php/php.module @@ -50,7 +50,7 @@ print t(\'Welcome visitor! Thank you for visiting.\'); ') . ''; $output .= '
  • ' . t('

    To display the name of a registered user, use this instead:

    -global $user;
    +$user = $_SESSION->user;
     if ($user->uid) {
       print t(\'Welcome @name! Thank you for visiting.\', array(\'@name\' => $user->name));
     }
    diff --git modules/poll/poll.module modules/poll/poll.module
    index 9c79fcc..bb5e626 100644
    --- modules/poll/poll.module
    +++ modules/poll/poll.module
    @@ -193,7 +193,7 @@ function poll_node_info() {
      * Implementation of hook_form().
      */
     function poll_form(&$node, $form_state) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       $admin = user_access('administer nodes') || user_access('edit any poll content') || (user_access('edit own poll content') && $user->uid == $node->uid);
     
    @@ -450,7 +450,7 @@ function poll_validate($node) {
      * Implementation of hook_load().
      */
     function poll_load($nodes) {
    -  global $user;
    +  $user = $_SESSION->user;
       foreach ($nodes as $node) {
         $poll = db_query("SELECT runtime, active FROM {poll} WHERE nid = :nid", array(':nid' => $node->nid))->fetch();
     
    @@ -542,7 +542,7 @@ function poll_delete($node) {
      *   rendering of the poll.
      */
     function poll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
    -  global $user;
    +  $user = $_SESSION->user;
       $output = '';
     
       // Special display for side-block
    @@ -644,7 +644,7 @@ function poll_vote($form, &$form_state) {
       $node = $form['#node'];
       $choice = $form_state['values']['choice'];
     
    -  global $user;
    +  $user = $_SESSION->user;
       if ($user->uid) {
         db_query('INSERT INTO {poll_vote} (nid, chid, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
       }
    @@ -818,7 +818,7 @@ function poll_cancel_form(&$form_state, $nid) {
      */
     function poll_cancel($form, &$form_state) {
       $node = node_load($form['#nid']);
    -  global $user;
    +  $user = $_SESSION->user;
     
       if ($user->uid) {
         db_query('DELETE FROM {poll_vote} WHERE nid = %d and uid = %d', $node->nid, $user->uid);
    diff --git modules/profile/profile.module modules/profile/profile.module
    index 792fc5f..bbb769e 100644
    --- modules/profile/profile.module
    +++ modules/profile/profile.module
    @@ -330,7 +330,7 @@ function profile_view_profile(&$user) {
       profile_load_profile($user);
     
       // Show private fields to administrators and people viewing their own account.
    -  if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) {
    +  if (user_access('administer users') || $_SESSION->user->uid == $user->uid) {
         $result = db_query('SELECT * FROM {profile_field} WHERE visibility != %d ORDER BY category, weight', PROFILE_HIDDEN);
       }
       else {
    diff --git modules/simpletest/drupal_web_test_case.php modules/simpletest/drupal_web_test_case.php
    index f5af622..bbe75a3 100644
    --- modules/simpletest/drupal_web_test_case.php
    +++ modules/simpletest/drupal_web_test_case.php
    @@ -495,7 +495,7 @@ class DrupalWebTestCase {
           $defaults['date'] = format_date($defaults['created'], 'custom', 'Y-m-d H:i:s O');
         }
         if (empty($settings['uid'])) {
    -      global $user;
    +      $user = $_SESSION->user;
           $defaults['uid'] = $user->uid;
         }
         $node = ($settings + $defaults);
    diff --git modules/simpletest/tests/file.test modules/simpletest/tests/file.test
    index 6990dd8..701f6d7 100644
    --- modules/simpletest/tests/file.test
    +++ modules/simpletest/tests/file.test
    @@ -254,12 +254,11 @@ class FileValidatorTest extends DrupalWebTestCase {
        * Test file_validate_size().
        */
       function testFileValidateSize() {
    -    global $user;
    +    $user = $_SESSION->user;
         $original_user = $user;
    -    drupal_save_session(FALSE);
     
         // Run these test as uid = 1.
    -    $user = user_load(array('uid' => 1));
    +    $_SESSION->user = user_load(array('uid' => 1));
     
         $file = new stdClass();
         $file->filesize = 999999;
    @@ -267,7 +266,7 @@ class FileValidatorTest extends DrupalWebTestCase {
         $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File');
     
         // Run these tests as a regular user.
    -    $user = $this->drupalCreateUser();
    +    $_SESSION->user = $this->drupalCreateUser();
     
         // Create a file with a size of 1000 bytes, and quotas of only 1 byte.
         $file = new stdClass();
    @@ -281,8 +280,7 @@ class FileValidatorTest extends DrupalWebTestCase {
         $errors = file_validate_size($file, 1, 1);
         $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File');
     
    -    $user = $original_user;
    -    drupal_save_session(TRUE);
    +    $_SESSION->user = $original_user;
       }
     }
     
    diff --git modules/simpletest/tests/session.test modules/simpletest/tests/session.test
    index b1946a3..8906bc1 100644
    --- modules/simpletest/tests/session.test
    +++ modules/simpletest/tests/session.test
    @@ -20,15 +20,9 @@ class SessionTestCase extends DrupalWebTestCase {
       }
     
       /**
    -   * Tests for drupal_save_session() and drupal_session_regenerate().
    +   * Tests for Session::regenerate().
        */
       function testSessionSaveRegenerate() {
    -    $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when initially called with no arguments.'), t('Session'));
    -    $this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session'));
    -    $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session'));
    -    $this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session'));
    -    $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session'));
    -
         // Test session hardening code from SA-2008-044.
         $user = $this->drupalCreateUser(array('access content'));
         // Enable sessions.
    @@ -88,7 +82,6 @@ class SessionTestCase extends DrupalWebTestCase {
         $this->drupalGet('session-test/no-set/' . $value_2);
         $this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
         $this->drupalGet('session-test/get');
    -    $this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));
     
         // Switch browser cookie to anonymous user, then back to user 1.
         $this->sessionReset();
    @@ -113,7 +106,6 @@ class SessionTestCase extends DrupalWebTestCase {
         $this->drupalGet('session-test/no-set/' . $value_4);
         $this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
         $this->drupalGet('session-test/get');
    -    $this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));
     
         // Logout and get first user back in. Sessions shouldn't persist through
         // logout, so the data won't be on the page.
    diff --git modules/simpletest/tests/session_test.module modules/simpletest/tests/session_test.module
    index e7efbf2..7afed6c 100644
    --- modules/simpletest/tests/session_test.module
    +++ modules/simpletest/tests/session_test.module
    @@ -60,7 +60,7 @@ function _session_test_set($value) {
      * anyway.
      */
     function _session_test_no_set($value) {
    -  drupal_save_session(FALSE);
    +  $_SESSION->preventWrite = TRUE;
       _session_test_set($value);
       return t('session saving was disabled, and then %val was set', array('%val' => $value));
     }
    diff --git modules/statistics/statistics.module modules/statistics/statistics.module
    index 4687046..f4a9ee5 100644
    --- modules/statistics/statistics.module
    +++ modules/statistics/statistics.module
    @@ -43,7 +43,7 @@ function statistics_help($path, $arg) {
      * This is where statistics are gathered on page accesses.
      */
     function statistics_exit() {
    -  global $user;
    +  $user = $_SESSION->user;
     
       drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
     
    diff --git modules/system/system.api.php modules/system/system.api.php
    index 55c21bd..dc6b6ec 100644
    --- modules/system/system.api.php
    +++ modules/system/system.api.php
    @@ -970,7 +970,7 @@ function hook_modules_uninstalled($modules) {
      *   The unaliased Drupal path that is being linked to.
      */
     function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       // Change all 'node' to 'article'.
       if (preg_match('|^node(/.*)|', $path, $matches)) {
    diff --git modules/system/system.module modules/system/system.module
    index 155c5fb..1671f57 100644
    --- modules/system/system.module
    +++ modules/system/system.module
    @@ -814,7 +814,7 @@ function system_user_login(&$edit, &$user, $category = NULL) {
      * Add the time zone field to the user edit and register forms.
      */
     function system_user_timezone(&$edit, &$form) {
    -  global $user;
    +  $user = $_SESSION->user;
       $form['timezone'] = array(
         '#type' => 'fieldset',
         '#title' => t('Locale settings'),
    @@ -1436,7 +1436,7 @@ function confirm_form($form, $question, $path, $description = NULL, $yes = NULL,
      * Determine if a user is in compact mode.
      */
     function system_admin_compact_mode() {
    -  global $user;
    +  $user = $_SESSION->user;
       return (isset($user->admin_compact_mode)) ? $user->admin_compact_mode : variable_get('admin_compact_mode', FALSE);
     }
     
    @@ -1447,7 +1447,7 @@ function system_admin_compact_mode() {
      *   Valid values are 'on' and 'off'.
      */
     function system_admin_compact_page($mode = 'off') {
    -  global $user;
    +  $user = $_SESSION->user;
       user_save($user, array('admin_compact_mode' => ($mode == 'on')));
       drupal_goto(drupal_get_destination());
     }
    @@ -1930,7 +1930,7 @@ function system_send_email_action_submit($form, $form_state) {
      * Implementation of a configurable Drupal action. Sends an email.
      */
     function system_send_email_action($object, $context) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       switch ($context['hook']) {
         case 'nodeapi':
    @@ -2053,7 +2053,7 @@ function system_message_action_submit($form, $form_state) {
      * A configurable Drupal action. Sends a message to the current user's screen.
      */
     function system_message_action(&$object, $context = array()) {
    -  global $user;
    +  $user = $_SESSION->user;
       $variables = array(
         '%site_name' => variable_get('site_name', 'Drupal'),
         '%username' => $user->name ? $user->name : variable_get('anonymous', t('Anonymous')),
    diff --git modules/tracker/tracker.module modules/tracker/tracker.module
    index fa2ee62..1bf0dd9 100644
    --- modules/tracker/tracker.module
    +++ modules/tracker/tracker.module
    @@ -61,7 +61,7 @@ function tracker_menu() {
      */
     function _tracker_myrecent_access($account) {
       // This path is only allowed for authenticated users looking at their own posts.
    -  return $account->uid && ($GLOBALS['user']->uid == $account->uid) && user_access('access content');
    +  return $account->uid && ($_SESSION->user->uid == $account->uid) && user_access('access content');
     }
     
     /**
    diff --git modules/trigger/trigger.test modules/trigger/trigger.test
    index c539ad5..a3469fc 100644
    --- modules/trigger/trigger.test
    +++ modules/trigger/trigger.test
    @@ -21,7 +21,7 @@ class TriggerContentTestCase extends DrupalWebTestCase {
        * Various tests, all in one function to assure they happen in the right order.
        */
       function testActionsContent() {
    -    global $user;
    +    $user = $_SESSION->user;
         $content_actions = array('node_publish_action', 'node_unpublish_action', 'node_make_sticky_action', 'node_make_unsticky_action', 'node_promote_action', 'node_unpromote_action');
     
         foreach ($content_actions as $action) {
    diff --git modules/upload/upload.module modules/upload/upload.module
    index 30beec7..ed4affc 100644
    --- modules/upload/upload.module
    +++ modules/upload/upload.module
    @@ -172,7 +172,7 @@ function upload_file_download($filepath) {
      *   A node object to associate with uploaded files.
      */
     function upload_node_form_submit(&$form, &$form_state) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       $limits = _upload_file_limits($user);
       $validators = array(
    @@ -486,7 +486,7 @@ function upload_save(&$node) {
     }
     
     function _upload_form($node) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       $form = array(
         '#theme' => 'upload_form_new',
    diff --git modules/user/user.module modules/user/user.module
    index 637a204..6260689 100644
    --- modules/user/user.module
    +++ modules/user/user.module
    @@ -126,9 +126,7 @@ function user_external_login($account, $edit = array()) {
       }
     
       // Valid login.
    -  global $user;
    -  $user = $account;
    -  user_authenticate_finalize($state['values']);
    +  $_SESSION->userLogin($account, $state['values']);
       return TRUE;
     }
     
    @@ -275,14 +273,16 @@ function user_save($account, $edit = array(), $category = 'account') {
     
         // Delete a blocked user's sessions to kick them if they are online.
         if (isset($edit['status']) && $edit['status'] == 0) {
    -      drupal_session_destroy_uid($account->uid);
    +      DrupalSession::destroyByUid($account->uid);
         }
     
         // If the password changed, delete all open sessions and recreate
         // the current one.
         if (!empty($edit['pass'])) {
    -      drupal_session_destroy_uid($account->uid);
    -      drupal_session_regenerate();
    +      DrupalSession::destroyByUid($account->uid);
    +      if ($account->uid == $_SESSION->user->uid) {
    +        $_SESSION->regenerate();
    +      }
         }
     
         // Refresh user object.
    @@ -522,7 +522,7 @@ function user_role_permissions($roles = array(), $reset = FALSE) {
      * can perform all actions.
      */
     function user_access($string, $account = NULL, $reset = FALSE) {
    -  global $user;
    +  $user = $_SESSION->user;
       static $perm = array();
     
       if ($reset) {
    @@ -736,7 +736,7 @@ function user_login_block() {
      * Implementation of hook_block().
      */
     function user_block($op = 'list', $delta = '', $edit = array()) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       if ($op == 'list') {
         $blocks['login']['info'] = t('User login');
    @@ -893,11 +893,11 @@ function theme_user_list($users, $title = NULL) {
     
     function user_is_anonymous() {
       // Menu administrators can see items for anonymous when administering.
    -  return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']);
    +  return !$_SESSION->user->uid || !empty($GLOBALS['menu_admin']);
     }
     
     function user_is_logged_in() {
    -  return (bool)$GLOBALS['user']->uid;
    +  return (bool) $_SESSION->user->uid;
     }
     
     function user_register_access() {
    @@ -908,7 +908,7 @@ function user_view_access($account) {
       return $account && $account->uid &&
         (
           // Always let users view their own profile.
    -      ($GLOBALS['user']->uid == $account->uid) ||
    +      ($_SESSION->user->uid == $account->uid) ||
           // Administrators can view all accounts.
           user_access('administer users') ||
           // The user is not blocked and logged in at least once.
    @@ -920,11 +920,11 @@ function user_view_access($account) {
      * Access callback for user account editing.
      */
     function user_edit_access($account) {
    -  return (($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
    +  return (($_SESSION->user->uid == $account->uid) || user_access('administer users')) && $account->uid > 0;
     }
     
     function user_load_self($arg) {
    -  $arg[1] = user_load($GLOBALS['user']->uid);
    +  $arg[1] = user_load($_SESSION->user->uid);
       return $arg;
     }
     
    @@ -1110,7 +1110,7 @@ function user_init() {
     }
     
     function user_uid_optional_load($arg) {
    -  return user_load(isset($arg) ? $arg : $GLOBALS['user']->uid);
    +  return user_load(isset($arg) ? $arg : $_SESSION->user->uid);
     }
     
     /**
    @@ -1159,14 +1159,14 @@ function user_uid_optional_to_arg($arg) {
       // Give back the current user uid when called from eg. tracker, aka.
       // with an empty arg. Also use the current user uid when called from
       // the menu with a % for the current account link.
    -  return empty($arg) || $arg == '%' ? $GLOBALS['user']->uid : $arg;
    +  return empty($arg) || $arg == '%' ? $_SESSION->user->uid : $arg;
     }
     
     /**
      * Menu item title callback - use the user name if it's not the current user.
      */
     function user_page_title($account) {
    -  if ($account->uid == $GLOBALS['user']->uid) {
    +  if ($account->uid == $_SESSION->user->uid) {
         return t('My account');
       }
       return $account->name;
    @@ -1229,7 +1229,7 @@ function user_set_authmaps($account, $authmaps) {
      * @ingroup forms
      */
     function user_login(&$form_state) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       // If we are already logged on, go to the user page instead.
       if ($user->uid) {
    @@ -1291,7 +1291,7 @@ function user_login_name_validate($form, &$form_state) {
     
     /**
      * A validate handler on the login form. Check supplied username/password
    - * against local users table. If successful, sets the global $user object.
    + * against local users table. If successful, sets the $user = $_SESSION->user object.
      */
     function user_login_authenticate_validate($form, &$form_state) {
       user_authenticate($form_state['values']);
    @@ -1302,7 +1302,7 @@ function user_login_authenticate_validate($form, &$form_state) {
      * error if user has not been authenticated yet.
      */
     function user_login_final_validate($form, &$form_state) {
    -  global $user;
    +  $user = $_SESSION->user;
       if (!$user->uid) {
         form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password'))));
         watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
    @@ -1320,8 +1320,6 @@ function user_login_final_validate($form, &$form_state) {
      *  A $user object, if successful.
      */
     function user_authenticate($form_values = array()) {
    -  global $user;
    -
       $password = trim($form_values['pass']);
       // Name and pass keys are required.
       if (!empty($form_values['name']) && !empty($password)) {
    @@ -1337,45 +1335,20 @@ function user_authenticate($form_values = array()) {
                }
             }
             $account = user_load(array('uid' => $account->uid, 'status' => 1));
    -        $user = $account;
    -        user_authenticate_finalize($form_values);
    -        return $user;
    +        return $_SESSION->userLogin($account, $form_values);
           }
         }
       }
     }
     
     /**
    - * Finalize the login process. Must be called when logging in a user.
    - *
    - * The function records a watchdog message about the new session, saves the
    - * login timestamp, calls hook_user op 'login' and generates a new session.
    - *
    - * $param $edit
    - *   This array is passed to hook_user op login.
    - */
    -function user_authenticate_finalize(&$edit) {
    -  global $user;
    -  watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
    -  // Update the user table timestamp noting user has logged in.
    -  // This is also used to invalidate one-time login links.
    -  $user->login = REQUEST_TIME;
    -  db_query("UPDATE {users} SET login = %d WHERE uid = %d", $user->login, $user->uid);
    -  // Regenerate the session ID to prevent against session fixation attacks.
    -  // This is called before hook_user in case one of those functions fails
    -  // or incorrectly does a redirect which would leave the old session in place.
    -  drupal_session_regenerate();
    -  user_module_invoke('login', $edit, $user);
    -}
    -
    -/**
      * Submit handler for the login form. Redirects the user to a page.
      *
      * The user is redirected to the My Account page. Setting the destination in
      * the query string (as done by the user login block) overrides the redirect.
      */
     function user_login_submit($form, &$form_state) {
    -  global $user;
    +  $user = $_SESSION->user;
       if ($user->uid) {
         $form_state['redirect'] = 'user/' . $user->uid;
         return;
    @@ -1384,15 +1357,13 @@ function user_login_submit($form, &$form_state) {
     
     /**
      * Helper function for authentication modules. Either login in or registers
    - * the current user, based on username. Either way, the global $user object is
    + * the current user, based on username. Either way, the $user = $_SESSION->user object is
      * populated based on $name.
      */
     function user_external_login_register($name, $module) {
    -  global $user;
    -
       $existing_user = user_load(array('name' => $name));
       if (isset($existing_user->uid)) {
    -    $user = $existing_user;
    +    $_SESSION->user = $existing_user;
       }
       else {
         // Register this new user.
    @@ -1410,8 +1381,8 @@ function user_external_login_register($name, $module) {
           return;
         }
         user_set_authmaps($account, array("authname_$module" => $name));
    -    $user = $account;
    -    watchdog('user', 'New external user: %name using module %module.', array('%name' => $name, '%module' => $module), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $user->uid . '/edit'));
    +    $_SESSION->user = $account;
    +    watchdog('user', 'New external user: %name using module %module.', array('%name' => $name, '%module' => $module), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $_SESSION->user->uid . '/edit'));
       }
     }
     
    @@ -1577,7 +1548,7 @@ function _user_edit_submit($uid, &$edit) {
      */
     function user_delete($edit, $uid) {
       $account = user_load(array('uid' => $uid));
    -  drupal_session_destroy_uid($uid);
    +  DrupalSession::destroyByUid($uid);
       _user_mail_notify('status_deleted', $account);
       module_invoke_all('user_delete', $edit, $account);
       db_query('DELETE FROM {users} WHERE uid = %d', $uid);
    @@ -1873,7 +1844,7 @@ function user_multiple_delete_confirm_submit($form, &$form_state) {
      * Implementation of hook_help().
      */
     function user_help($path, $arg) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       switch ($path) {
         case 'admin/help#user':
    @@ -2147,7 +2118,7 @@ function _user_mail_notify($op, $account, $language = NULL) {
      */
     function _user_password_dynamic_validation() {
       static $complete = FALSE;
    -  global $user;
    +  $user = $_SESSION->user;
       // Only need to do once per page.
       if (!$complete) {
         drupal_add_js(drupal_get_path('module', 'user') . '/user.js');
    @@ -2227,11 +2198,11 @@ function user_block_user_action(&$object, $context = array()) {
         $uid = $context['uid'];
       }
       else {
    -    global $user;
    +    $user = $_SESSION->user;
         $uid = $user->uid;
       }
       db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid);
    -  drupal_session_destroy_uid($uid);
    +  DrupalSession::destroyByUid($uid);
       watchdog('action', 'Blocked user %name.', array('%name' => check_plain($user->name)));
     }
     
    @@ -2348,7 +2319,7 @@ function user_register_submit($form, &$form_state) {
      * @see user_register_submit()
      */
     function user_register() {
    -  global $user;
    +  $user = $_SESSION->user;
     
       $admin = user_access('administer users');
     
    diff --git modules/user/user.pages.inc modules/user/user.pages.inc
    index fa5c992..0e780a0 100644
    --- modules/user/user.pages.inc
    +++ modules/user/user.pages.inc
    @@ -74,10 +74,10 @@ function user_pass_submit($form, &$form_state) {
      * Menu callback; process one time login link and redirects to the user page on success.
      */
     function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action = NULL) {
    -  global $user;
    +  $user = $_SESSION->user;
     
       // Check if the user is already logged in. The back button is often the culprit here.
    -  if ($user->uid) {
    +  if ($_SESSION->user->uid) {
         drupal_set_message(t('You have already used this one-time login link. It is not necessary to use this link to login anymore. You are already logged in.'));
         drupal_goto();
       }
    @@ -96,11 +96,10 @@ function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action =
             // First stage is a confirmation form, then login
             if ($action == 'login') {
               watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
    -          // Set the new user.
    -          $user = $account;
    -          // user_authenticate_finalize() also updates the login timestamp of the
    -          // user, which invalidates further use of the one-time login link.
    -          user_authenticate_finalize($form_state['values']);
    +          // Session singleton takes care of updating relevant timestamp and
    +          // session data, and registers the provided account as the new user.
    +          // One one-time login link will also be invalidated.
    +          $_SESSION->userLogin($account, $form_state['values']);
               drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'));
               drupal_goto('user/' . $user->uid . '/edit');
             }
    @@ -129,17 +128,7 @@ function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action =
      * Menu callback; logs the current user out, and redirects to the home page.
      */
     function user_logout() {
    -  global $user;
    -
    -  watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
    -
    -  // Destroy the current session:
    -  session_destroy();
    -  module_invoke_all('user_logout', NULL, $user);
    -
    -  // Load the anonymous user
    -  $user = drupal_anonymous_user();
    -
    +  $_SESSION->userLogout();
       drupal_goto();
     }
     
    @@ -354,7 +343,7 @@ function user_edit_submit($form, &$form_state) {
      * users.
      */
     function user_page() {
    -  global $user;
    +  $user = $_SESSION->user;
       if ($user->uid) {
         menu_set_active_item('user/' . $user->uid);
         return menu_execute_active_handler();