diff --git a/libraries/cms/application/cms.php b/libraries/cms/application/cms.php index ad46c24904b58..c460f0082f237 100644 --- a/libraries/cms/application/cms.php +++ b/libraries/cms/application/cms.php @@ -178,34 +178,57 @@ public function checkSession() // If the session record doesn't exist initialise it. if (!$exists) { - $query->clear(); + // Get the session handler from the configuration. + $handler = $this->get('session_handler', 'none'); if ($session->isNew()) { - $query->insert($db->quoteName('#__session')) - ->columns($db->quoteName('session_id') . ', ' . $db->quoteName('client_id') . ', ' . $db->quoteName('time')) - ->values($db->quote($session->getId()) . ', ' . (int) $this->getClientId() . ', ' . $db->quote((int) time())); - $db->setQuery($query); + // Default column/value set + $columns = array($db->quoteName('session_id'), $db->quoteName('client_id')); + $values = array($db->quote($session->getId()), (int) $this->getClientId()); + + // If the database session handler is not in use, append the time to the row + if ($handler != 'database') + { + $columns[] = $db->quoteName('time'); + $values[] = (int) time(); + } } else { - $query->insert($db->quoteName('#__session')) - ->columns( - $db->quoteName('session_id') . ', ' . $db->quoteName('client_id') . ', ' . $db->quoteName('guest') . ', ' . - $db->quoteName('time') . ', ' . $db->quoteName('userid') . ', ' . $db->quoteName('username') - ) - ->values( - $db->quote($session->getId()) . ', ' . (int) $this->getClientId() . ', ' . (int) $user->get('guest') . ', ' . - $db->quote((int) $session->get('session.timer.start')) . ', ' . (int) $user->get('id') . ', ' . $db->quote($user->get('username')) - ); - - $db->setQuery($query); + // Default column/value set + $columns = array( + $db->quoteName('session_id'), + $db->quoteName('client_id'), + $db->quoteName('guest'), + $db->quoteName('userid'), + $db->quoteName('username') + ); + $values = array( + $db->quote($session->getId()), + (int) $this->getClientId(), + (int) $user->guest, + (int) $user->id, + $db->quote($user->username) + ); + + // If the database session handler is not in use, append the time to the row + if ($handler != 'database') + { + $columns[] = $db->quoteName('time'); + $values[] = (int) $session->get('session.timer.start'); + } } // If the insert failed, exit the application. try { - $db->execute(); + $db->setQuery( + $db->getQuery(true) + ->insert($db->quoteName('#__session')) + ->columns($columns) + ->values(implode(', ', $values)) + )->execute(); } catch (RuntimeException $e) { @@ -748,28 +771,40 @@ public function loadSession(JSession $session = null) $session->start(); // TODO: At some point we need to get away from having session data always in the db. - $db = JFactory::getDbo(); - - // Remove expired sessions from the database. + $db = JFactory::getDbo(); $time = time(); - if ($time % 2) - { - // The modulus introduces a little entropy, making the flushing less accurate - // but fires the query less than half the time. - $query = $db->getQuery(true) - ->delete($db->quoteName('#__session')) - ->where($db->quoteName('time') . ' < ' . $db->quote((int) ($time - $session->getExpire()))); - - $db->setQuery($query); - $db->execute(); - } - // Get the session handler from the configuration. $handler = $this->get('session_handler', 'none'); - if (($handler != 'database' && ($time % 2 || $session->isNew())) - || ($handler == 'database' && $session->isNew())) + // Purge expired session data if not using the database handler; the handler will run garbage collection as a native part of PHP's API + if ($handler != 'database' && $time % 2) + { + // The modulus introduces a little entropy, making the flushing less accurate but fires the query less than half the time. + try + { + $db->setQuery( + $db->getQuery(true) + ->delete($db->quoteName('#__session')) + ->where($db->quoteName('time') . ' < ' . $db->quote((int) ($time - $session->getExpire()))) + )->execute(); + } + catch (RuntimeException $e) + { + /* + * The database API logs errors on failures so we don't need to add any error handling mechanisms here. + * Since garbage collection does not result in a fatal error when run in the session API, we don't allow it here either. + */ + } + } + + /* + * Check for extra session metadata when: + * + * 1) The database handler is in use and the session is new + * 2) The database handler is not in use and the time is an even numbered second or the session is new + */ + if (($handler != 'database' && ($time % 2 || $session->isNew())) || ($handler == 'database' && $session->isNew())) { $this->checkSession(); } diff --git a/libraries/joomla/session/storage/database.php b/libraries/joomla/session/storage/database.php index 664480a304921..926168bd90025 100644 --- a/libraries/joomla/session/storage/database.php +++ b/libraries/joomla/session/storage/database.php @@ -17,6 +17,68 @@ */ class JSessionStorageDatabase extends JSessionStorage { + /** + * Flag whether gc() has been called + * + * @var boolean + * @since 3.5 + */ + private $gcCalled = false; + + /** + * Lifetime for garbage collection + * + * @var integer + * @since 3.5 + */ + private $gcLifetime; + + /** + * Close the session + * + * @return boolean True on success, false otherwise + * + * @since 3.5 + */ + public function close() + { + $db = JFactory::getDbo(); + + if ($this->gcCalled) + { + $query = $db->getQuery(true) + ->delete($db->quoteName('#__session')) + ->where($db->quoteName('time') . ' < ' . $db->quote((int) $this->gcLifetime)); + + // Remove expired sessions from the database. + $db->setQuery($query)->execute(); + + $this->gcCalled = false; + $this->gcLifetime = null; + } + + $db->disconnect(); + + return true; + } + + /** + * Initialize session + * + * @param string $save_path The path where to store/retrieve the session + * @param string $id The session id + * + * @return boolean True on success, false otherwise + * + * @since 3.5 + */ + public function open($save_path, $id) + { + JFactory::getDbo()->connect(); + + return true; + } + /** * Read the data for a particular session identifier from the SessionHandler backend. * @@ -36,8 +98,8 @@ public function read($id) // Get the session data from the database table. $query = $db->getQuery(true) ->select($db->quoteName('data')) - ->from($db->quoteName('#__session')) - ->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); + ->from($db->quoteName('#__session')) + ->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); $db->setQuery($query); @@ -49,7 +111,7 @@ public function read($id) } catch (Exception $e) { - return false; + return ''; } } @@ -72,23 +134,33 @@ public function write($id, $data) try { + // Figure out if a row exists for the session ID $query = $db->getQuery(true) - ->update($db->quoteName('#__session')) - ->set($db->quoteName('data') . ' = ' . $db->quote($data)) - ->set($db->quoteName('time') . ' = ' . $db->quote((int) time())) + ->select($db->quoteName('session_id')) + ->from($db->quoteName('#__session')) ->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); - // Try to update the session data in the database table. - $db->setQuery($query); + $idExists = $db->setQuery($query)->loadResult(); + + $query = $db->getQuery(true); - if (!$db->execute()) + if ($idExists) { - return false; + $query->update($db->quoteName('#__session')) + ->set($db->quoteName('data') . ' = ' . $db->quote($data)) + ->set($db->quoteName('time') . ' = ' . $db->quote((int) time())) + ->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); } - /* Since $db->execute did not throw an exception, so the query was successful. - Either the data changed, or the data was identical. - In either case we are done. - */ + else + { + $query->insert($db->quoteName('#__session')) + ->columns(array($db->quoteName('data'), $db->quoteName('time'), $db->quoteName('session_id'))) + ->values(implode(', ', array($db->quote($data), (int) time(), $db->quote($id)))); + } + + // Try to insert the session data in the database table. + $db->setQuery($query)->execute(); + return true; } catch (Exception $e) @@ -118,9 +190,9 @@ public function destroy($id) ->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); // Remove a session from the database. - $db->setQuery($query); + $db->setQuery($query)->execute(); - return (boolean) $db->execute(); + return true; } catch (Exception $e) { @@ -139,26 +211,10 @@ public function destroy($id) */ public function gc($lifetime = 1440) { - // Get the database connection object and verify its connected. - $db = JFactory::getDbo(); - - // Determine the timestamp threshold with which to purge old sessions. - $past = time() - $lifetime; - - try - { - $query = $db->getQuery(true) - ->delete($db->quoteName('#__session')) - ->where($db->quoteName('time') . ' < ' . $db->quote((int) $past)); + // We'll delay garbage collection until the session is closed to prevent potential issues mid-cycle + $this->gcLifetime = time() - $lifetime; + $this->gcCalled = true; - // Remove expired sessions from the database. - $db->setQuery($query); - - return (boolean) $db->execute(); - } - catch (Exception $e) - { - return false; - } + return true; } }