torrentpier-lts/library/Zend/Session/SessionManager.php

453 lines
13 KiB
PHP

<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 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;
use Zend\Stdlib\ArrayUtils;
/**
* 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
* @param array $validators
* @throws Exception\RuntimeException
*/
public function __construct(
Config\ConfigInterface $config = null,
Storage\StorageInterface $storage = null,
SaveHandler\SaveHandlerInterface $saveHandler = null,
array $validators = array()
) {
parent::__construct($config, $storage, $saveHandler, $validators);
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);
}
$oldSessionData = array();
if (isset($_SESSION)) {
$oldSessionData = $_SESSION;
}
session_start();
if ($oldSessionData instanceof \Traversable
|| (! empty($oldSessionData) && is_array($oldSessionData))
) {
$_SESSION = ArrayUtils::merge($oldSessionData, $_SESSION, true);
}
$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);
}
$this->initializeValidatorChain();
if (!$this->isValid()) {
throw new Exception\RuntimeException('Session validation failed');
}
}
/**
* Create validators, insert reference value and add them to the validator chain
*/
protected function initializeValidatorChain()
{
$validatorChain = $this->getValidatorChain();
$validatorValues = $this->getStorage()->getMetadata('_VALID');
foreach ($this->validators as $validator) {
// Ignore validators which are already present in Storage
if (is_array($validatorValues) && array_key_exists($validator, $validatorValues)) {
continue;
}
$validator = new $validator(null);
$validatorChain->attach('session.validate', array($validator, 'isValid'));
}
}
/**
* 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->trigger('session.validate', $this, array($this), function ($test) {
return false === $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')
);
}
}