/symphony/lib/core/class.session.php
PHP | 331 lines | 160 code | 33 blank | 138 comment | 23 complexity | 5befe03131915bbe5ac11d7dbcde1093 MD5 | raw file
- <?php
- /**
- * @package core
- */
- /**
- * The Session class is a handler for all Session related logic in PHP. The functions
- * map directly to all handler functions as defined by session_set_save_handler in
- * PHP. In Symphony, this function is used in conjunction with the `Cookie` class.
- * Based on: http://php.net/manual/en/function.session-set-save-handler.php#81761
- * by klose at openriverbed dot de which was based on
- * http://php.net/manual/en/function.session-set-save-handler.php#79706 by
- * maria at junkies dot jp
- *
- * @link http://php.net/manual/en/function.session-set-save-handler.php
- */
- class Session
- {
- /**
- * If a Session has been created, this will be true, otherwise false
- *
- * @var boolean
- */
- private static $_initialized = false;
- /**
- * Disallow public construction
- */
- private function __construct()
- {
- }
- /**
- * Starts a Session object, only if one doesn't already exist. This function maps
- * the Session Handler functions to this classes methods by reading the default
- * information from the PHP ini file.
- *
- * @link http://php.net/manual/en/function.session-set-save-handler.php
- * @link http://php.net/manual/en/function.session-set-cookie-params.php
- * @param integer $lifetime
- * How long a Session is valid for, by default this is 0, which means it
- * never expires
- * @param string $path
- * The path the cookie is valid for on the domain
- * @param string $domain
- * The domain this cookie is valid for
- * @param boolean $httpOnly
- * Whether this cookie can be read by Javascript. By default the cookie
- * cannot be read by Javascript
- * @throws Exception
- * @return string|boolean
- * Returns the Session ID on success, or false on error.
- */
- public static function start($lifetime = 0, $path = '/', $domain = null, $httpOnly = true)
- {
- if (!self::$_initialized) {
- if (!is_object(Symphony::Database()) || !Symphony::Database()->isConnected()) {
- throw new Exception('Failed to start session, no Database found.');
- }
- // Get config
- $gcDivisor = Symphony::Configuration()->get('session_gc_divisor', 'symphony');
- $strictDomain = Symphony::Configuration()->get('session_strict_domain', 'symphony') === 'yes';
- // Set php parameters
- if (session_id() == '' && !headers_sent()) {
- ini_set('session.use_trans_sid', '0');
- ini_set('session.use_strict_mode', '1');
- ini_set('session.use_only_cookies', '1');
- ini_set('session.gc_maxlifetime', $lifetime);
- ini_set('session.gc_probability', '1');
- ini_set('session.gc_divisor', $gcDivisor);
- }
- // Register handler
- $handler = new Session;
- session_set_save_handler(
- [$handler ,'open'],
- [$handler ,'close'],
- [$handler ,'read'],
- [$handler ,'write'],
- [$handler ,'destroy'],
- [$handler ,'gc']
- );
- // Set cookie parameters
- if ($strictDomain) {
- // setting the domain to null makes the cookie valid for the current host only
- $domain = null;
- } else {
- $domain = $domain ? : $handler->getDomain();
- }
- session_set_cookie_params(
- $lifetime,
- $handler->createCookieSafePath($path),
- $domain,
- defined('__SECURE__') && __SECURE__,
- $httpOnly
- );
- session_cache_limiter('');
- if (!session_id()) {
- if (headers_sent()) {
- throw new Exception('Headers already sent. Cannot start session.');
- }
- register_shutdown_function('session_write_close');
- session_start();
- }
- self::$_initialized = true;
- }
- return session_id();
- }
- /**
- * Returns a properly formatted ascii string for the cookie path.
- * Browsers are notoriously bad at parsing the cookie path. They do not
- * respect the content-encoding header. So we must be careful when dealing
- * with setups with special characters in their paths.
- *
- * @since Symphony 2.7.0
- **/
- protected function createCookieSafePath($path)
- {
- $path = array_filter(explode('/', $path));
- if (empty($path)) {
- return '/';
- }
- $path = array_map('rawurlencode', $path);
- return '/' . implode('/', $path);
- }
- /**
- * Returns the current domain for the Session to be saved to, if the installation
- * is on localhost, this returns null and just allows PHP to take care of setting
- * the valid domain for the Session, otherwise it will return the non-www version
- * of the domain host.
- *
- * @return string|null
- * Null if on localhost, or HTTP_HOST is not set, a string of the domain name sans
- * www otherwise
- */
- public function getDomain()
- {
- if (HTTP_HOST) {
- if (preg_match('/(localhost|127\.0\.0\.1)/', HTTP_HOST)) {
- return null; // prevent problems on local setups
- }
- // Remove leading www and ending :port
- return preg_replace('/(^www\.|:\d+$)/i', null, HTTP_HOST);
- }
- return null;
- }
- /**
- * Allows the Session to open without any further logic.
- *
- * @return boolean
- * Always returns true
- */
- public function open()
- {
- return true;
- }
- /**
- * Allows the Session to close without any further logic. Acts as a
- * destructor function for the Session.
- *
- * @return boolean
- * Always returns true
- */
- public function close()
- {
- return true;
- }
- /**
- * Given an ID, and some data, save it into `tbl_sessions`. This uses
- * the ID as a unique key, and will override any existing data. If the
- * `$data` is deemed to be empty, no row will be saved in the database
- * unless there is an existing row.
- *
- * @param string $id
- * The ID of the Session, usually a hash
- * @param string $data
- * The Session information, usually a serialized object of
- * `$_SESSION[Cookie->_index]`
- * @throws DatabaseException
- * @return boolean
- * true if the Session information was saved successfully, false otherwise
- */
- public function write($id, $data)
- {
- // Only prevent this record from saving if there isn't already a record
- // in the database. This prevents empty Sessions from being created, but
- // allows them to be nulled.
- $session_data = $this->read($id);
- if (!$session_data) {
- $empty = true;
- if (function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
- $unserialized_data = $this->unserialize($data);
- foreach ($unserialized_data as $d) {
- if (!empty($d)) {
- $empty = false;
- }
- }
- if ($empty) {
- return true;
- }
- // PHP 7.0 makes the session inactive in write callback,
- // so we try to detect empty sessions without decoding them
- } elseif ($data === Symphony::Configuration()->get('cookie_prefix', 'symphony') . '|a:0:{}') {
- return true;
- }
- }
- $fields = array(
- 'session' => $id,
- 'session_expires' => time(),
- 'session_data' => $data
- );
- return Symphony::Database()
- ->insert('tbl_sessions')
- ->values($fields)
- ->updateOnDuplicateKey()
- ->execute()
- ->success();
- }
- /**
- * Given raw session data return the unserialized array.
- * Used to check if the session is really empty before writing.
- *
- * @since Symphony 2.3.3
- * @param string $data
- * The serialized session data
- * @return array
- * The unserialised session data
- */
- private function unserialize($data)
- {
- $hasBuffer = isset($_SESSION);
- $buffer = $_SESSION;
- session_decode($data);
- $session = $_SESSION;
- if ($hasBuffer) {
- $_SESSION = $buffer;
- } else {
- unset($_SESSION);
- }
- return $session;
- }
- /**
- * Given a session's ID, return it's row from `tbl_sessions`
- *
- * @param string $id
- * The identifier for the Session to fetch
- * @return string
- * The serialised session data
- */
- public function read($id)
- {
- if (!$id) {
- return null;
- }
- return Symphony::Database()
- ->select(['session_data'])
- ->from('tbl_sessions')
- ->where(['session' => $id])
- ->limit(1)
- ->execute()
- ->string('session_data');
- }
- /**
- * Given a session's ID, remove it's row from `tbl_sessions`
- *
- * @param string $id
- * The identifier for the Session to destroy
- * @throws DatabaseException
- * @return boolean
- * true if the Session was deleted successfully, false otherwise
- */
- public function destroy($id)
- {
- if (!$id) {
- return true;
- }
- return Symphony::Database()
- ->delete('tbl_sessions')
- ->where(['session' => $id])
- ->execute()
- ->success();
- }
- /**
- * The garbage collector, which removes all empty Sessions, or any
- * Sessions that have expired. This has a 10% chance of firing based
- * off the `gc_probability`/`gc_divisor`.
- *
- * @param integer $max
- * The max session lifetime.
- * @throws DatabaseException
- * @return boolean
- * true on Session deletion, false if an error occurs
- */
- public function gc($max)
- {
- return Symphony::Database()
- ->delete('tbl_sessions')
- ->where(['session_expires' => ['<=' => time() - $max]])
- ->execute()
- ->success();
- }
- }