PK ]QK8%
LICENSE.mdnu ٘ Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Laminas Foundation nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK ]QF[ [ COPYRIGHT.mdnu ٘ Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)
PK ]Q%! .travis.ymlnu ٘ language: php
cache:
directories:
- $HOME/.composer/cache
env:
global:
- COMPOSER_ARGS="--no-interaction"
- COVERAGE_DEPS="php-coveralls/php-coveralls"
- TESTS_LAMINAS_SESSION_ADAPTER_DRIVER_MYSQL=true
- TESTS_LAMINAS_SESSION_ADAPTER_DRIVER_PGSQL=true
services:
- mongodb
- mysql
- postgresql
matrix:
fast_finish: true
include:
- php: 5.6
env:
- DEPS=lowest
- php: 5.6
env:
- DEPS=latest
- php: 7
env:
- DEPS=lowest
- php: 7
env:
- DEPS=latest
- php: 7.1
env:
- DEPS=lowest
- php: 7.1
env:
- DEPS=latest
- CS_CHECK=true
- TEST_COVERAGE=true
- php: 7.2
env:
- DEPS=lowest
- php: 7.2
env:
- DEPS=latest
- php: 7.3
env:
- DEPS=lowest
- php: 7.3
env:
- DEPS=latest
- php: 7.4
env:
- DEPS=latest
before_install:
- if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi
- pecl channel-update pecl.php.net
- pecl -q upgrade mongodb
- $(php -m | grep -q mongodb) || echo "extension=mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- psql -c 'create database laminas_session_test;' -U postgres
- mysql -e 'CREATE DATABASE laminas_session_test;'
install:
- travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs
- if [[ $LEGACY_DEPS != '' ]]; then travis_retry composer update --with-dependencies $COMPOSER_ARGS $LEGACY_DEPS ; fi
- if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi
- if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi
- if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS $COVERAGE_DEPS ; fi
- stty cols 120 && composer show
script:
- if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi
- if [[ $CS_CHECK == 'true' ]]; then composer cs-check ; fi
after_script:
- if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry php vendor/bin/php-coveralls -v ; fi
notifications:
email: false
PK ]Qx= = .coveralls.ymlnu ٘ coverage_clover: clover.xml
json_path: coveralls-upload.json
PK ]QhYݬj j phpcs.xmlnu ٘
src
test
/src/ValidatorChain.php
PK ]Ql& README.mdnu ٘ # laminas-session
[![Build Status](https://travis-ci.com/laminas/laminas-session.svg?branch=master)](https://travis-ci.com/laminas/laminas-session)
[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-session/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-session?branch=master)
laminas-session manages PHP sessions using an object oriented interface.
- File issues at https://github.com/laminas/laminas-session/issues
- Documentation is at https://docs.laminas.dev/laminas-session/
PK ]Q@ݫ
.gitignorenu ٘ /clover.xml
/composer.lock
/coveralls-upload.json
/docs/html/
/laminas-mkdoc-theme.tgz
/laminas-mkdoc-theme/
/phpunit.xml
/vendor/
PK ]QY1M M src/Container.phpnu ٘ offsetExists($key)) {
return $ret;
}
$storage = $this->getStorage();
$name = $this->getName();
$ret =& $storage[$name][$key];
return $ret;
}
}
PK ]Qz " src/Exception/RuntimeException.phpnu ٘ setCacheStorage($cacheStorage);
}
/**
* Open Session
*
* @param string $savePath
* @param string $name
* @return bool
*/
public function open($savePath, $name)
{
// @todo figure out if we want to use these
$this->sessionSavePath = $savePath;
$this->sessionName = $name;
return true;
}
/**
* Close session
*
* @return bool
*/
public function close()
{
return true;
}
/**
* Read session data
*
* @param string $id
* @return string
*/
public function read($id)
{
return (string) $this->getCacheStorage()->getItem($id);
}
/**
* Write session data
*
* @param string $id
* @param string $data
* @return bool
*/
public function write($id, $data)
{
return $this->getCacheStorage()->setItem($id, $data);
}
/**
* Destroy session
*
* @param string $id
* @return bool
*/
public function destroy($id)
{
$this->getCacheStorage()->getItem($id, $exists);
if (! (bool) $exists) {
return true;
}
return (bool) $this->getCacheStorage()->removeItem($id);
}
/**
* Garbage Collection
*
* @param int $maxlifetime
* @return bool
*/
public function gc($maxlifetime)
{
$cache = $this->getCacheStorage();
if ($cache instanceof ClearExpiredCacheStorage) {
return $cache->clearExpired();
}
return true;
}
/**
* Set cache storage
*
* @param CacheStorage $cacheStorage
* @return Cache
*/
public function setCacheStorage(CacheStorage $cacheStorage)
{
$this->cacheStorage = $cacheStorage;
return $this;
}
/**
* Get cache storage
*
* @return CacheStorage
*/
public function getCacheStorage()
{
return $this->cacheStorage;
}
/**
* @deprecated Misspelled method - use getCacheStorage() instead
*/
public function getCacheStorge()
{
return $this->getCacheStorage();
}
}
PK ]Q퓎 " src/SaveHandler/DbTableGateway.phpnu ٘ tableGateway = $tableGateway;
$this->options = $options;
}
/**
* Open Session
*
* @param string $savePath
* @param string $name
* @return bool
*/
public function open($savePath, $name)
{
$this->sessionSavePath = $savePath;
$this->sessionName = $name;
$this->lifetime = ini_get('session.gc_maxlifetime');
return true;
}
/**
* Close session
*
* @return bool
*/
public function close()
{
return true;
}
/**
* Read session data
*
* @param string $id
* @param bool $destroyExpired Optional; true by default
* @return string
*/
public function read($id, $destroyExpired = true)
{
$row = $this->tableGateway->select([
$this->options->getIdColumn() => $id,
$this->options->getNameColumn() => $this->sessionName,
])->current();
if ($row) {
if ($row->{$this->options->getModifiedColumn()} +
$row->{$this->options->getLifetimeColumn()} > time()) {
return (string) $row->{$this->options->getDataColumn()};
}
if ($destroyExpired) {
$this->destroy($id);
}
}
return '';
}
/**
* Write session data
*
* @param string $id
* @param string $data
* @return bool
*/
public function write($id, $data)
{
$data = [
$this->options->getModifiedColumn() => time(),
$this->options->getDataColumn() => (string) $data,
];
$rows = $this->tableGateway->select([
$this->options->getIdColumn() => $id,
$this->options->getNameColumn() => $this->sessionName,
])->current();
if ($rows) {
return (bool) $this->tableGateway->update($data, [
$this->options->getIdColumn() => $id,
$this->options->getNameColumn() => $this->sessionName,
]);
}
$data[$this->options->getLifetimeColumn()] = $this->lifetime;
$data[$this->options->getIdColumn()] = $id;
$data[$this->options->getNameColumn()] = $this->sessionName;
return (bool) $this->tableGateway->insert($data);
}
/**
* Destroy session
*
* @param string $id
* @return bool
*/
public function destroy($id)
{
$this->tableGateway->delete([
$this->options->getIdColumn() => $id,
$this->options->getNameColumn() => $this->sessionName,
]);
return true;
}
/**
* Garbage Collection
*
* @param int $maxlifetime
* @return true
*/
public function gc($maxlifetime)
{
$platform = $this->tableGateway->getAdapter()->getPlatform();
return (bool) $this->tableGateway->delete(
sprintf(
'%s < %d',
$platform->quoteIdentifier($this->options->getModifiedColumn()),
(time() - $this->lifetime)
)
);
}
}
PK ]Q:l7 ( src/SaveHandler/SaveHandlerInterface.phpnu ٘ getDatabase())) {
throw new InvalidArgumentException('The database option cannot be empty');
}
if (null === ($collection = $options->getCollection())) {
throw new InvalidArgumentException('The collection option cannot be empty');
}
$this->mongoClient = $mongoClient;
$this->options = $options;
}
/**
* Open session
*
* @param string $savePath
* @param string $name
* @return bool
*/
public function open($savePath, $name)
{
// Note: session save path is not used
$this->sessionName = $name;
$this->lifetime = (int) ini_get('session.gc_maxlifetime');
$this->mongoCollection = $this->mongoClient->selectCollection(
$this->options->getDatabase(),
$this->options->getCollection()
);
$this->mongoCollection->createIndex(
[$this->options->getModifiedField() => 1],
$this->options->useExpireAfterSecondsIndex() ? ['expireAfterSeconds' => $this->lifetime] : []
);
return true;
}
/**
* Close session
*
* @return bool
*/
public function close()
{
return true;
}
/**
* Read session data
*
* @param string $id
* @return string
*/
public function read($id)
{
$session = $this->mongoCollection->findOne([
'_id' => $id,
$this->options->getNameField() => $this->sessionName,
]);
if (null !== $session) {
// check if session has expired if index is not used
if (! $this->options->useExpireAfterSecondsIndex()) {
$timestamp = $session[$this->options->getLifetimeField()];
$timestamp += floor(((string)$session[$this->options->getModifiedField()]) / 1000);
// session expired
if ($timestamp <= time()) {
$this->destroy($id);
return '';
}
}
return $session[$this->options->getDataField()]->getData();
}
return '';
}
/**
* Write session data
*
* @param string $id
* @param string $data
* @return bool
*/
public function write($id, $data)
{
$saveOptions = array_replace(
$this->options->getSaveOptions(),
['upsert' => true, 'multiple' => false]
);
$criteria = [
'_id' => $id,
$this->options->getNameField() => $this->sessionName,
];
$newObj = [
'$set' => [
$this->options->getDataField() => new Binary((string)$data, Binary::TYPE_GENERIC),
$this->options->getLifetimeField() => $this->lifetime,
$this->options->getModifiedField() => new UTCDatetime(floor(microtime(true) * 1000)),
],
];
/* Note: a MongoCursorException will be thrown if a record with this ID
* already exists with a different session name, since the upsert query
* cannot insert a new document with the same ID and new session name.
* This should only happen if ID's are not unique or if the session name
* is altered mid-process.
*/
$result = $this->mongoCollection->updateOne($criteria, $newObj, $saveOptions);
return $result->isAcknowledged();
}
/**
* Destroy session
*
* @param string $id
* @return bool
*/
public function destroy($id)
{
$result = $this->mongoCollection->deleteOne(
[
'_id' => $id,
$this->options->getNameField() => $this->sessionName,
],
$this->options->getSaveOptions()
);
return $result->isAcknowledged();
}
/**
* Garbage collection
*
* Note: MongoDB 2.2+ supports TTL collections, which may be used in place
* of this method by indexing the "modified" field with an
* "expireAfterSeconds" option. Regardless of whether TTL collections are
* used, consider indexing this field to make the remove query more
* efficient.
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
* @param int $maxlifetime
* @return bool
*/
public function gc($maxlifetime)
{
/* Note: unlike DbTableGateway, we do not use the lifetime field in
* each document. Doing so would require a $where query to work with the
* computed value (modified + lifetime) and be very inefficient.
*/
$microseconds = floor(microtime(true) * 1000) - $maxlifetime * 1000;
$result = $this->mongoCollection->deleteMany(
[
$this->options->getModifiedField() => ['$lt' => new UTCDateTime($microseconds)],
],
$this->options->getSaveOptions()
);
return $result->isAcknowledged();
}
}
PK ]Qp+ " src/SaveHandler/MongoDBOptions.phpnu ٘ 1];
/**
* Name field
*
* @var string
*/
protected $nameField = 'name';
/**
* Data field
*
* @var string
*/
protected $dataField = 'data';
/**
* Lifetime field
*
* @var string
*/
protected $lifetimeField = 'lifetime';
/**
* Modified field
*
* @var string
*/
protected $modifiedField = 'modified';
/**
* Use expireAfterSeconds index
*
* @var bool
*/
protected $useExpireAfterSecondsIndex = false;
/**
* {@inheritdoc}
*/
public function __construct($options = null)
{
parent::__construct($options);
$mongoVersion = phpversion('mongo') ?: '0.0.0';
if ($this->saveOptions === ['w' => 1] && version_compare($mongoVersion, '1.3.0', '<')) {
$this->saveOptions = ['safe' => true];
}
}
/**
* Override AbstractOptions::__set
*
* Validates value if save options are being set.
*
* @param string $key
* @param mixed $value
*/
public function __set($key, $value)
{
if (strtolower($key) !== 'saveoptions') {
return parent::__set($key, $value);
}
if (! is_array($value)) {
throw new InvalidArgumentException('Expected array for save options');
}
$this->setSaveOptions($value);
}
/**
* Set database name
*
* @param string $database
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setDatabase($database)
{
$database = (string) $database;
if (strlen($database) === 0) {
throw new InvalidArgumentException('$database must be a non-empty string');
}
$this->database = $database;
return $this;
}
/**
* Get database name
*
* @return string
*/
public function getDatabase()
{
return $this->database;
}
/**
* Set collection name
*
* @param string $collection
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setCollection($collection)
{
$collection = (string) $collection;
if (strlen($collection) === 0) {
throw new InvalidArgumentException('$collection must be a non-empty string');
}
$this->collection = $collection;
return $this;
}
/**
* Get collection name
*
* @return string
*/
public function getCollection()
{
return $this->collection;
}
/**
* Set save options
*
* @see http://php.net/manual/en/mongocollection.save.php
* @param array $saveOptions
* @return MongoDBOptions
*/
public function setSaveOptions(array $saveOptions)
{
$this->saveOptions = $saveOptions;
return $this;
}
/**
* Get save options
*
* @return string
*/
public function getSaveOptions()
{
return $this->saveOptions;
}
/**
* Set name field
*
* @param string $nameField
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setNameField($nameField)
{
$nameField = (string) $nameField;
if (strlen($nameField) === 0) {
throw new InvalidArgumentException('$nameField must be a non-empty string');
}
$this->nameField = $nameField;
return $this;
}
/**
* Get name field
*
* @return string
*/
public function getNameField()
{
return $this->nameField;
}
/**
* Set data field
*
* @param string $dataField
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setDataField($dataField)
{
$dataField = (string) $dataField;
if (strlen($dataField) === 0) {
throw new InvalidArgumentException('$dataField must be a non-empty string');
}
$this->dataField = $dataField;
return $this;
}
/**
* Get data field
*
* @return string
*/
public function getDataField()
{
return $this->dataField;
}
/**
* Set lifetime field
*
* @param string $lifetimeField
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setLifetimeField($lifetimeField)
{
$lifetimeField = (string) $lifetimeField;
if (strlen($lifetimeField) === 0) {
throw new InvalidArgumentException('$lifetimeField must be a non-empty string');
}
$this->lifetimeField = $lifetimeField;
return $this;
}
/**
* Get lifetime Field
*
* @return string
*/
public function getLifetimeField()
{
return $this->lifetimeField;
}
/**
* Set Modified Field
*
* @param string $modifiedField
* @return MongoDBOptions
* @throws InvalidArgumentException
*/
public function setModifiedField($modifiedField)
{
$modifiedField = (string) $modifiedField;
if (strlen($modifiedField) === 0) {
throw new InvalidArgumentException('$modifiedField must be a non-empty string');
}
$this->modifiedField = $modifiedField;
return $this;
}
/**
* Get modified Field
*
* @return string
*/
public function getModifiedField()
{
return $this->modifiedField;
}
/**
* @return boolean
*/
public function useExpireAfterSecondsIndex()
{
return $this->useExpireAfterSecondsIndex;
}
/**
* Enable expireAfterSeconds index.
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
* @param boolean $useExpireAfterSecondsIndex
*/
public function setUseExpireAfterSecondsIndex($useExpireAfterSecondsIndex)
{
$this->useExpireAfterSecondsIndex = (bool) $useExpireAfterSecondsIndex;
}
}
PK ]Q ) src/SaveHandler/DbTableGatewayOptions.phpnu ٘ idColumn = $idColumn;
return $this;
}
/**
* Get Id Column
*
* @return string
*/
public function getIdColumn()
{
return $this->idColumn;
}
/**
* Set Name Column
*
* @param string $nameColumn
* @return DbTableGatewayOptions
* @throws Exception\InvalidArgumentException
*/
public function setNameColumn($nameColumn)
{
$nameColumn = (string) $nameColumn;
if (strlen($nameColumn) === 0) {
throw new Exception\InvalidArgumentException('$nameColumn must be a non-empty string');
}
$this->nameColumn = $nameColumn;
return $this;
}
/**
* Get Name Column
*
* @return string
*/
public function getNameColumn()
{
return $this->nameColumn;
}
/**
* Set Data Column
*
* @param string $dataColumn
* @return DbTableGatewayOptions
* @throws Exception\InvalidArgumentException
*/
public function setDataColumn($dataColumn)
{
$dataColumn = (string) $dataColumn;
if (strlen($dataColumn) === 0) {
throw new Exception\InvalidArgumentException('$dataColumn must be a non-empty string');
}
$this->dataColumn = $dataColumn;
return $this;
}
/**
* Get Data Column
*
* @return string
*/
public function getDataColumn()
{
return $this->dataColumn;
}
/**
* Set Lifetime Column
*
* @param string $lifetimeColumn
* @return DbTableGatewayOptions
* @throws Exception\InvalidArgumentException
*/
public function setLifetimeColumn($lifetimeColumn)
{
$lifetimeColumn = (string) $lifetimeColumn;
if (strlen($lifetimeColumn) === 0) {
throw new Exception\InvalidArgumentException('$lifetimeColumn must be a non-empty string');
}
$this->lifetimeColumn = $lifetimeColumn;
return $this;
}
/**
* Get Lifetime Column
*
* @return string
*/
public function getLifetimeColumn()
{
return $this->lifetimeColumn;
}
/**
* Set Modified Column
*
* @param string $modifiedColumn
* @return DbTableGatewayOptions
* @throws Exception\InvalidArgumentException
*/
public function setModifiedColumn($modifiedColumn)
{
$modifiedColumn = (string) $modifiedColumn;
if (strlen($modifiedColumn) === 0) {
throw new Exception\InvalidArgumentException('$modifiedColumn must be a non-empty string');
}
$this->modifiedColumn = $modifiedColumn;
return $this;
}
/**
* Get Modified Column
*
* @return string
*/
public function getModifiedColumn()
{
return $this->modifiedColumn;
}
}
PK ]Q{q] ] src/AbstractManager.phpnu ٘ defaultConfigClass)) {
throw new Exception\RuntimeException(sprintf(
'Unable to locate config class "%s"; class does not exist',
$this->defaultConfigClass
));
}
$config = new $this->defaultConfigClass();
if (! $config instanceof Config) {
throw new Exception\RuntimeException(sprintf(
'Default config class %s is invalid; must implement %s\Config\ConfigInterface',
$this->defaultConfigClass,
__NAMESPACE__
));
}
}
$this->config = $config;
// init storage
if ($storage === null) {
if (! class_exists($this->defaultStorageClass)) {
throw new Exception\RuntimeException(sprintf(
'Unable to locate storage class "%s"; class does not exist',
$this->defaultStorageClass
));
}
$storage = new $this->defaultStorageClass();
if (! $storage instanceof Storage) {
throw new Exception\RuntimeException(sprintf(
'Default storage class %s is invalid; must implement %s\Storage\StorageInterface',
$this->defaultConfigClass,
__NAMESPACE__
));
}
}
$this->storage = $storage;
// save handler
if ($saveHandler !== null) {
$this->saveHandler = $saveHandler;
}
$this->validators = $validators;
}
/**
* Set configuration object
*
* @param Config $config
* @return AbstractManager
*/
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
/**
* Retrieve configuration object
*
* @return Config
*/
public function getConfig()
{
return $this->config;
}
/**
* Set session storage object
*
* @param Storage $storage
* @return AbstractManager
*/
public function setStorage(Storage $storage)
{
$this->storage = $storage;
return $this;
}
/**
* Retrieve storage object
*
* @return Storage
*/
public function getStorage()
{
return $this->storage;
}
/**
* Set session save handler object
*
* @param SaveHandler $saveHandler
* @return AbstractManager
*/
public function setSaveHandler(SaveHandler $saveHandler)
{
$this->saveHandler = $saveHandler;
return $this;
}
/**
* Get SaveHandler Object
*
* @return SaveHandler
*/
public function getSaveHandler()
{
return $this->saveHandler;
}
}
PK ]Qr-I7 7 src/SessionManager.phpnu ٘ true,
'clear_storage' => false,
];
/**
* @var array Default session manager options
*/
protected $defaultOptions = [
'attach_default_validators' => true,
];
/**
* @var array Default validators
*/
protected $defaultValidators = [
Validator\Id::class,
];
/**
* @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
* @param array $options
* @throws Exception\RuntimeException
*/
public function __construct(
Config\ConfigInterface $config = null,
Storage\StorageInterface $storage = null,
SaveHandler\SaveHandlerInterface $saveHandler = null,
array $validators = [],
array $options = []
) {
$options = array_merge($this->defaultOptions, $options);
if ($options['attach_default_validators']) {
$validators = array_merge($this->defaultValidators, $validators);
}
parent::__construct($config, $storage, $saveHandler, $validators);
register_shutdown_function([$this, 'writeClose']);
}
/**
* Does a session exist and is it currently active?
*
* @return bool
*/
public function sessionExists()
{
if (session_status() == PHP_SESSION_ACTIVE) {
return true;
}
$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 = [];
if (isset($_SESSION)) {
$oldSessionData = $_SESSION;
// convert session data to plain array that’ll be acceptable as
// ArrayUtils::merge parameter
if ($oldSessionData instanceof Storage\StorageInterface) {
$oldSessionData = $oldSessionData->toArray();
} elseif ($oldSessionData instanceof Traversable) {
$oldSessionData = iterator_to_array($oldSessionData);
}
}
session_start();
if (! 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', [$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)
{
if ($this->sessionExists()) {
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();
$event = new Event();
$event->setName('session.validate');
$event->setTarget($this);
$event->setParams($this);
$falseResult = function ($test) {
return false === $test;
};
$responses = $validator->triggerEventUntil($falseResult, $event);
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($saveHandler);
}
}
PK ]Q' $ src/Validator/ValidatorInterface.phpnu ٘ storage = $storage;
$validators = $storage->getMetadata('_VALID');
if ($validators) {
foreach ($validators as $validator => $data) {
$this->attachValidator('session.validate', [new $validator($data), 'isValid'], 1);
}
}
}
/**
* Attach a listener to the session validator chain.
*
* @param string $event
* @param null|callable $callback
* @param int $priority
* @return \Laminas\Stdlib\CallbackHandler
*/
public function attach($event, $callback = null, $priority = 1)
{
return $this->attachValidator($event, $callback, $priority);
}
}
PK ]Qt