- <?php
- /**
- * Zend Framework (http://framework.zend.com/)
- *
- * @link http://github.com/zendframework/zf2 for the canonical source repository
- * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
-
- namespace Zend\Session;
-
- use Zend\EventManager\EventManagerInterface;
-
- /**
- * Session ManagerInterface implementation utilizing ext/session
- */
- class SessionManager extends AbstractManager
- {
- /**
- * Default options when a call to {@link destroy()} is made
- * - send_expire_cookie: whether or not to send a cookie expiring the current session cookie
- * - clear_storage: whether or not to empty the storage object of any stored values
- * @var array
- */
- protected $defaultDestroyOptions = array(
- 'send_expire_cookie' => true,
- 'clear_storage' => false,
- );
-
- /**
- * @var string value returned by session_name()
- */
- protected $name;
-
- /**
- * @var EventManagerInterface Validation chain to determine if session is valid
- */
- protected $validatorChain;
-
- /**
- * Constructor
- *
- * @param Config\ConfigInterface|null $config
- * @param Storage\StorageInterface|null $storage
- * @param SaveHandler\SaveHandlerInterface|null $saveHandler
- * @throws Exception\RuntimeException
- */
- public function __construct(Config\ConfigInterface $config = null, Storage\StorageInterface $storage = null, SaveHandler\SaveHandlerInterface $saveHandler = null)
- {
- parent::__construct($config, $storage, $saveHandler);
- register_shutdown_function(array($this, 'writeClose'));
- }
-
- /**
- * Does a session exist and is it currently active?
- *
- * @return bool
- */
- public function sessionExists()
- {
- $sid = defined('SID') ? constant('SID') : false;
- if ($sid !== false && $this->getId()) {
- return true;
- }
- if (headers_sent()) {
- return true;
- }
- return false;
- }
-
- /**
- * Start session
- *
- * if No session currently exists, attempt to start it. Calls
- * {@link isValid()} once session_start() is called, and raises an
- * exception if validation fails.
- *
- * @param bool $preserveStorage If set to true, current session storage will not be overwritten by the
- * contents of $_SESSION.
- * @return void
- * @throws Exception\RuntimeException
- */
- public function start($preserveStorage = false)
- {
- if ($this->sessionExists()) {
- return;
- }
-
- $saveHandler = $this->getSaveHandler();
- if ($saveHandler instanceof SaveHandler\SaveHandlerInterface) {
- // register the session handler with ext/session
- $this->registerSaveHandler($saveHandler);
- }
-
- session_start();
-
- $storage = $this->getStorage();
-
- // Since session is starting, we need to potentially repopulate our
- // session storage
- if ($storage instanceof Storage\SessionStorage && $_SESSION !== $storage) {
- if (!$preserveStorage) {
- $storage->fromArray($_SESSION);
- }
- $_SESSION = $storage;
- } elseif ($storage instanceof Storage\StorageInitializationInterface) {
- $storage->init($_SESSION);
- }
-
- if (!$this->isValid()) {
- throw new Exception\RuntimeException('Session validation failed');
- }
- }
-
- /**
- * Destroy/end a session
- *
- * @param array $options See {@link $defaultDestroyOptions}
- * @return void
- */
- public function destroy(array $options = null)
- {
- if (!$this->sessionExists()) {
- return;
- }
-
- if (null === $options) {
- $options = $this->defaultDestroyOptions;
- } else {
- $options = array_merge($this->defaultDestroyOptions, $options);
- }
-
- session_destroy();
- if ($options['send_expire_cookie']) {
- $this->expireSessionCookie();
- }
-
- if ($options['clear_storage']) {
- $this->getStorage()->clear();
- }
- }
-
- /**
- * Write session to save handler and close
- *
- * Once done, the Storage object will be marked as isImmutable.
- *
- * @return void
- */
- public function writeClose()
- {
- // The assumption is that we're using PHP's ext/session.
- // session_write_close() will actually overwrite $_SESSION with an
- // empty array on completion -- which leads to a mismatch between what
- // is in the storage object and $_SESSION. To get around this, we
- // temporarily reset $_SESSION to an array, and then re-link it to
- // the storage object.
- //
- // Additionally, while you _can_ write to $_SESSION following a
- // session_write_close() operation, no changes made to it will be
- // flushed to the session handler. As such, we now mark the storage
- // object isImmutable.
- $storage = $this->getStorage();
- if (!$storage->isImmutable()) {
- $_SESSION = $storage->toArray(true);
- session_write_close();
- $storage->fromArray($_SESSION);
- $storage->markImmutable();
- }
- }
-
- /**
- * Attempt to set the session name
- *
- * If the session has already been started, or if the name provided fails
- * validation, an exception will be raised.
- *
- * @param string $name
- * @return SessionManager
- * @throws Exception\InvalidArgumentException
- */
- public function setName($name)
- {
- if ($this->sessionExists()) {
- throw new Exception\InvalidArgumentException(
- 'Cannot set session name after a session has already started'
- );
- }
-
- if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
- throw new Exception\InvalidArgumentException(
- 'Name provided contains invalid characters; must be alphanumeric only'
- );
- }
-
- $this->name = $name;
- session_name($name);
- return $this;
- }
-
- /**
- * Get session name
- *
- * Proxies to {@link session_name()}.
- *
- * @return string
- */
- public function getName()
- {
- if (null === $this->name) {
- // If we're grabbing via session_name(), we don't need our
- // validation routine; additionally, calling setName() after
- // session_start() can lead to issues, and often we just need the name
- // in order to do things such as setting cookies.
- $this->name = session_name();
- }
- return $this->name;
- }
-
- /**
- * Set session ID
- *
- * Can safely be called in the middle of a session.
- *
- * @param string $id
- * @return SessionManager
- */
- public function setId($id)
- {
- if ($this->sessionExists()) {
- throw new Exception\RuntimeException('Session has already been started, to change the session ID call regenerateId()');
- }
- session_id($id);
- return $this;
- }
-
- /**
- * Get session ID
- *
- * Proxies to {@link session_id()}
- *
- * @return string
- */
- public function getId()
- {
- return session_id();
- }
-
- /**
- * Regenerate id
- *
- * Regenerate the session ID, using session save handler's
- * native ID generation Can safely be called in the middle of a session.
- *
- * @param bool $deleteOldSession
- * @return SessionManager
- */
- public function regenerateId($deleteOldSession = true)
- {
- session_regenerate_id((bool) $deleteOldSession);
- return $this;
- }
-
- /**
- * Set the TTL (in seconds) for the session cookie expiry
- *
- * Can safely be called in the middle of a session.
- *
- * @param null|int $ttl
- * @return SessionManager
- */
- public function rememberMe($ttl = null)
- {
- if (null === $ttl) {
- $ttl = $this->getConfig()->getRememberMeSeconds();
- }
- $this->setSessionCookieLifetime($ttl);
- return $this;
- }
-
- /**
- * Set a 0s TTL for the session cookie
- *
- * Can safely be called in the middle of a session.
- *
- * @return SessionManager
- */
- public function forgetMe()
- {
- $this->setSessionCookieLifetime(0);
- return $this;
- }
-
- /**
- * Set the validator chain to use when validating a session
- *
- * In most cases, you should use an instance of {@link ValidatorChain}.
- *
- * @param EventManagerInterface $chain
- * @return SessionManager
- */
- public function setValidatorChain(EventManagerInterface $chain)
- {
- $this->validatorChain = $chain;
- return $this;
- }
-
- /**
- * Get the validator chain to use when validating a session
- *
- * By default, uses an instance of {@link ValidatorChain}.
- *
- * @return EventManagerInterface
- */
- public function getValidatorChain()
- {
- if (null === $this->validatorChain) {
- $this->setValidatorChain(new ValidatorChain($this->getStorage()));
- }
- return $this->validatorChain;
- }
-
- /**
- * Is this session valid?
- *
- * Notifies the Validator Chain until either all validators have returned
- * true or one has failed.
- *
- * @return bool
- */
- public function isValid()
- {
- $validator = $this->getValidatorChain();
- $responses = $validator->triggerUntil('session.validate', $this, array($this), function ($test) {
- return !$test;
- });
- if ($responses->stopped()) {
- // If execution was halted, validation failed
- return false;
- }
- // Otherwise, we're good to go
- return true;
- }
-
- /**
- * Expire the session cookie
- *
- * Sends a session cookie with no value, and with an expiry in the past.
- *
- * @return void
- */
- public function expireSessionCookie()
- {
- $config = $this->getConfig();
- if (!$config->getUseCookies()) {
- return;
- }
- setcookie(
- $this->getName(), // session name
- '', // value
- $_SERVER['REQUEST_TIME'] - 42000, // TTL for cookie
- $config->getCookiePath(),
- $config->getCookieDomain(),
- $config->getCookieSecure(),
- $config->getCookieHttpOnly()
- );
- }
-
- /**
- * Set the session cookie lifetime
- *
- * If a session already exists, destroys it (without sending an expiration
- * cookie), regenerates the session ID, and restarts the session.
- *
- * @param int $ttl
- * @return void
- */
- protected function setSessionCookieLifetime($ttl)
- {
- $config = $this->getConfig();
- if (!$config->getUseCookies()) {
- return;
- }
-
- // Set new cookie TTL
- $config->setCookieLifetime($ttl);
-
- if ($this->sessionExists()) {
- // There is a running session so we'll regenerate id to send a new cookie
- $this->regenerateId();
- }
- }
-
- /**
- * Register Save Handler with ext/session
- *
- * Since ext/session is coupled to this particular session manager
- * register the save handler with ext/session.
- *
- * @param SaveHandler\SaveHandlerInterface $saveHandler
- * @return bool
- */
- protected function registerSaveHandler(SaveHandler\SaveHandlerInterface $saveHandler)
- {
- return session_set_save_handler(
- array($saveHandler, 'open'),
- array($saveHandler, 'close'),
- array($saveHandler, 'read'),
- array($saveHandler, 'write'),
- array($saveHandler, 'destroy'),
- array($saveHandler, 'gc')
- );
- }
- }