diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php index 084cb16..355d9fb 100644 --- a/core/lib/Drupal/Core/Session/SessionManager.php +++ b/core/lib/Drupal/Core/Session/SessionManager.php @@ -13,11 +13,14 @@ use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\Session\SessionHandler; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; /** * Manages user sessions. */ -class SessionManager implements SessionManagerInterface { +class SessionManager extends NativeSessionStorage { /** * The current request. @@ -49,14 +52,14 @@ class SessionManager implements SessionManagerInterface { * * @var bool */ - protected static $enabled = TRUE; + protected static $globalEnabled = TRUE; /** * Whether the session has been started. * * @var bool */ - protected static $started = FALSE; + protected static $globalStarted = FALSE; /** * Constructs a new session manager instance. @@ -66,9 +69,21 @@ class SessionManager implements SessionManagerInterface { * @param \Drupal\Core\Database\Connection $connection * The database connection. */ - public function __construct(Request $request, Connection $connection) { + public function __construct(Request $request, Connection $connection, MetadataBag $metadata_bag = NULL) { + // Register the default session handler. + // @todo Extract session storage from session handler into a service. + $handler = new SessionHandler($this, $request, $connection); + + $options = array(); + parent::__construct($options, $handler, $metadata_bag); $this->request = $request; $this->connection = $connection; + + // @todo: When not using the Symfony Session object, the list of bags in the + // NativeSessionStorage will remain uninitialized. This will lead to errors + // in NativeSessionHandler::loadSession. Remove this as soon as we are using + // the Symfony session object. + $this->registerBag(new AttributeBag()); } /** @@ -77,11 +92,6 @@ public function __construct(Request $request, Connection $connection) { public function initialize() { global $user; - // Register the default session handler. - // @todo Extract session storage from session handler into a service. - $handler = new SessionHandler($this, $this->request, $this->connection); - session_set_save_handler($handler, TRUE); - $is_https = $this->request->isSecure(); $cookies = $this->request->cookies; if (($cookies->has(session_name()) && ($session_name = $cookies->get(session_name()))) || ($is_https && Settings::get('mixed_mode_sessions', FALSE) && ($cookies->has(substr(session_name(), 1))) && ($session_name = $cookies->get(substr(session_name(), 1))))) { @@ -90,7 +100,7 @@ public function initialize() { // anonymous users not use a session cookie unless something is stored in // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. $this->start(); - if ($user->isAuthenticated() || !empty($_SESSION)) { + if ($user->isAuthenticated() || !$this->isEmptySession()) { drupal_page_is_cacheable(FALSE); } } @@ -123,11 +133,12 @@ public function start() { if ($this->isCli() || $this->isStarted()) { return; } + // Save current session data before starting it, as PHP will destroy it. $session_data = isset($_SESSION) ? $_SESSION : NULL; - session_start(); - static::$started = TRUE; + parent::start(); + static::$globalStarted = TRUE; // Restore session data. if (!empty($session_data)) { @@ -146,7 +157,7 @@ public function save() { return; } - if ($user->isAnonymous() && empty($_SESSION)) { + if ($user->isAnonymous() && $this->isEmptySession()) { // There is no session data to store, destroy the session if it was // previously started. if ($this->isStarted()) { @@ -167,7 +178,7 @@ public function save() { } } // Write the session data. - session_write_close(); + parent::save(); } } @@ -175,13 +186,15 @@ public function save() { * {@inheritdoc} */ public function isStarted() { - return static::$started && session_status() === \PHP_SESSION_ACTIVE; + return (static::$globalStarted || parent::isStarted()) && session_status() === \PHP_SESSION_ACTIVE; } /** * {@inheritdoc} + * + * @todo: Remove this method and let the base class do the heavy-lifting. */ - public function regenerate() { + public function regenerate($destroy = FALSE, $lifetime = NULL) { global $user; // Nothing to do if we are not allowed to change the session. @@ -267,14 +280,14 @@ public function delete($uid) { * {@inheritdoc} */ public function isEnabled() { - return static::$enabled; + return static::$globalEnabled; } /** * {@inheritdoc} */ public function disable() { - static::$enabled = FALSE; + static::$globalEnabled = FALSE; return $this; } @@ -282,7 +295,7 @@ public function disable() { * {@inheritdoc} */ public function enable() { - static::$enabled = TRUE; + static::$globalEnabled = TRUE; return $this; } @@ -297,4 +310,45 @@ protected function isCli() { return PHP_SAPI === 'cli'; } + /** + * Test whether the session is empty. + * + * @return bool + * TRUE when the session does not contain any values. + */ + protected function isEmptySession(array $session = NULL) { + if (isset($_SESSION) && !isset($session)) { + $session = $_SESSION; + } + + if (empty($session)) { + return TRUE; + } + + $remaining_keys = array_flip(array_keys($session)); + + // Examine attribute bags. + foreach ($this->bags as $bag) { + // We only are able to examine attribute bags. Therefore when encountering + // a different bag, assume it contains data we may not simply remove. + if (!($bag instanceof AttributeBag)) { + return FALSE; + } + + $key = $bag->getStorageKey(); + if (!empty($session[$key])) { + return FALSE; + } + + unset($remaining_keys[$key]); + } + + // Ignore session metadata. + unset($remaining_keys[$this->metadataBag->getStorageKey()]); + + // If anything is remaining, this is data set directly on the $_SESSION + // superglobal. + return empty($remaining_keys); + } + } diff --git a/core/lib/Drupal/Core/Session/SessionManagerInterface.php b/core/lib/Drupal/Core/Session/SessionManagerInterface.php index 346bc0d..611b2c9 100644 --- a/core/lib/Drupal/Core/Session/SessionManagerInterface.php +++ b/core/lib/Drupal/Core/Session/SessionManagerInterface.php @@ -7,10 +7,12 @@ namespace Drupal\Core\Session; +use Symfony\Components\HttpFoundation\Session\SessionStorageInterface; + /** * Defines the session manager interface. */ -interface SessionManagerInterface { +interface SessionManagerInterface extends SessionStorageInterface { /** * Initializes the session handler, starting a session if needed. @@ -20,28 +22,6 @@ public function initialize(); /** - * Starts a session forcefully, preserving already set session data. - */ - public function start(); - - /** - * Commits the current session, if necessary. - * - * If an anonymous user already have an empty session, destroy it. - */ - public function save(); - - /** - * Returns whether a session has been started. - */ - public function isStarted(); - - /** - * Called when an anonymous user becomes authenticated or vice-versa. - */ - public function regenerate(); - - /** * Ends a specific user's session(s). * * @param int $uid