Update Kirby and dependencies
This commit is contained in:
parent
503b339974
commit
399fa20902
439 changed files with 66915 additions and 64442 deletions
|
@ -13,159 +13,159 @@ namespace Kirby\Session;
|
|||
*/
|
||||
class AutoSession
|
||||
{
|
||||
protected $sessions;
|
||||
protected $options;
|
||||
protected $sessions;
|
||||
protected $options;
|
||||
|
||||
protected $createdSession;
|
||||
protected $createdSession;
|
||||
|
||||
/**
|
||||
* Creates a new AutoSession instance
|
||||
*
|
||||
* @param \Kirby\Session\SessionStore|string $store SessionStore object or a path to the storage directory (uses the FileSessionStore)
|
||||
* @param array $options Optional additional options:
|
||||
* - `durationNormal`: Duration of normal sessions in seconds; defaults to 2 hours
|
||||
* - `durationLong`: Duration of "remember me" sessions in seconds; defaults to 2 weeks
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); *only* used for normal sessions; defaults to `1800` (half an hour)
|
||||
* - `cookieName`: Name to use for the session cookie; defaults to `kirby_session`
|
||||
* - `gcInterval`: How often should the garbage collector be run?; integer or `false` for never; defaults to `100`
|
||||
*/
|
||||
public function __construct($store, array $options = [])
|
||||
{
|
||||
// merge options with defaults
|
||||
$this->options = array_merge([
|
||||
'durationNormal' => 7200,
|
||||
'durationLong' => 1209600,
|
||||
'timeout' => 1800,
|
||||
'cookieName' => 'kirby_session',
|
||||
'gcInterval' => 100
|
||||
], $options);
|
||||
/**
|
||||
* Creates a new AutoSession instance
|
||||
*
|
||||
* @param \Kirby\Session\SessionStore|string $store SessionStore object or a path to the storage directory (uses the FileSessionStore)
|
||||
* @param array $options Optional additional options:
|
||||
* - `durationNormal`: Duration of normal sessions in seconds; defaults to 2 hours
|
||||
* - `durationLong`: Duration of "remember me" sessions in seconds; defaults to 2 weeks
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); *only* used for normal sessions; defaults to `1800` (half an hour)
|
||||
* - `cookieName`: Name to use for the session cookie; defaults to `kirby_session`
|
||||
* - `gcInterval`: How often should the garbage collector be run?; integer or `false` for never; defaults to `100`
|
||||
*/
|
||||
public function __construct($store, array $options = [])
|
||||
{
|
||||
// merge options with defaults
|
||||
$this->options = array_merge([
|
||||
'durationNormal' => 7200,
|
||||
'durationLong' => 1209600,
|
||||
'timeout' => 1800,
|
||||
'cookieName' => 'kirby_session',
|
||||
'gcInterval' => 100
|
||||
], $options);
|
||||
|
||||
// create an internal instance of the low-level Sessions class
|
||||
$this->sessions = new Sessions($store, [
|
||||
'cookieName' => $this->options['cookieName'],
|
||||
'gcInterval' => $this->options['gcInterval']
|
||||
]);
|
||||
}
|
||||
// create an internal instance of the low-level Sessions class
|
||||
$this->sessions = new Sessions($store, [
|
||||
'cookieName' => $this->options['cookieName'],
|
||||
'gcInterval' => $this->options['gcInterval']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the automatic session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `detect`: Whether to allow sessions in the `Authorization` HTTP header (`true`) or only in the session cookie (`false`); defaults to `false`
|
||||
* - `createMode`: When creating a new session, should it be set as a cookie or is it going to be transmitted manually to be used in a header?; defaults to `cookie`
|
||||
* - `long`: Whether the session is a long "remember me" session or a normal session; defaults to `false`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function get(array $options = [])
|
||||
{
|
||||
// merge options with defaults
|
||||
$options = array_merge([
|
||||
'detect' => false,
|
||||
'createMode' => 'cookie',
|
||||
'long' => false
|
||||
], $options);
|
||||
/**
|
||||
* Returns the automatic session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `detect`: Whether to allow sessions in the `Authorization` HTTP header (`true`) or only in the session cookie (`false`); defaults to `false`
|
||||
* - `createMode`: When creating a new session, should it be set as a cookie or is it going to be transmitted manually to be used in a header?; defaults to `cookie`
|
||||
* - `long`: Whether the session is a long "remember me" session or a normal session; defaults to `false`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function get(array $options = [])
|
||||
{
|
||||
// merge options with defaults
|
||||
$options = array_merge([
|
||||
'detect' => false,
|
||||
'createMode' => 'cookie',
|
||||
'long' => false
|
||||
], $options);
|
||||
|
||||
// determine expiry options based on the session type
|
||||
if ($options['long'] === true) {
|
||||
$duration = $this->options['durationLong'];
|
||||
$timeout = false;
|
||||
} else {
|
||||
$duration = $this->options['durationNormal'];
|
||||
$timeout = $this->options['timeout'];
|
||||
}
|
||||
// determine expiry options based on the session type
|
||||
if ($options['long'] === true) {
|
||||
$duration = $this->options['durationLong'];
|
||||
$timeout = false;
|
||||
} else {
|
||||
$duration = $this->options['durationNormal'];
|
||||
$timeout = $this->options['timeout'];
|
||||
}
|
||||
|
||||
// get the current session
|
||||
if ($options['detect'] === true) {
|
||||
$session = $this->sessions->currentDetected();
|
||||
} else {
|
||||
$session = $this->sessions->current();
|
||||
}
|
||||
// get the current session
|
||||
if ($options['detect'] === true) {
|
||||
$session = $this->sessions->currentDetected();
|
||||
} else {
|
||||
$session = $this->sessions->current();
|
||||
}
|
||||
|
||||
// create a new session
|
||||
if ($session === null) {
|
||||
$session = $this->createdSession ?? $this->sessions->create([
|
||||
'mode' => $options['createMode'],
|
||||
'startTime' => time(),
|
||||
'expiryTime' => time() + $duration,
|
||||
'timeout' => $timeout,
|
||||
'renewable' => true,
|
||||
]);
|
||||
// create a new session
|
||||
if ($session === null) {
|
||||
$session = $this->createdSession ?? $this->sessions->create([
|
||||
'mode' => $options['createMode'],
|
||||
'startTime' => time(),
|
||||
'expiryTime' => time() + $duration,
|
||||
'timeout' => $timeout,
|
||||
'renewable' => true,
|
||||
]);
|
||||
|
||||
// cache the newly created session to ensure that we don't create multiple
|
||||
$this->createdSession = $session;
|
||||
}
|
||||
// cache the newly created session to ensure that we don't create multiple
|
||||
$this->createdSession = $session;
|
||||
}
|
||||
|
||||
// update the session configuration if the $options changed
|
||||
// always use the less strict value for compatibility with features
|
||||
// that depend on the less strict behavior
|
||||
if ($duration > $session->duration()) {
|
||||
// the duration needs to be extended
|
||||
$session->duration($duration);
|
||||
}
|
||||
if ($session->timeout() !== false) {
|
||||
// a timeout exists
|
||||
if ($timeout === false) {
|
||||
// it needs to be completely disabled
|
||||
$session->timeout(false);
|
||||
} elseif (is_int($timeout) && $timeout > $session->timeout()) {
|
||||
// it needs to be extended
|
||||
$session->timeout($timeout);
|
||||
}
|
||||
}
|
||||
// update the session configuration if the $options changed
|
||||
// always use the less strict value for compatibility with features
|
||||
// that depend on the less strict behavior
|
||||
if ($duration > $session->duration()) {
|
||||
// the duration needs to be extended
|
||||
$session->duration($duration);
|
||||
}
|
||||
if ($session->timeout() !== false) {
|
||||
// a timeout exists
|
||||
if ($timeout === false) {
|
||||
// it needs to be completely disabled
|
||||
$session->timeout(false);
|
||||
} elseif (is_int($timeout) && $timeout > $session->timeout()) {
|
||||
// it needs to be extended
|
||||
$session->timeout($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// if the session has been created and was not yet initialized,
|
||||
// update the mode to a custom mode
|
||||
// don't update back to cookie mode because the "special" behavior always wins
|
||||
if ($session->token() === null && $options['createMode'] !== 'cookie') {
|
||||
$session->mode($options['createMode']);
|
||||
}
|
||||
// if the session has been created and was not yet initialized,
|
||||
// update the mode to a custom mode
|
||||
// don't update back to cookie mode because the "special" behavior always wins
|
||||
if ($session->token() === null && $options['createMode'] !== 'cookie') {
|
||||
$session->mode($options['createMode']);
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty session that is *not* automatically transmitted to the client
|
||||
* Useful for custom applications like a password reset link
|
||||
* Does *not* affect the automatic session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `startTime`: Time the session starts being valid (date string or timestamp); defaults to `now`
|
||||
* - `expiryTime`: Time the session expires (date string or timestamp); defaults to `+ 2 hours`
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); defaults to `1800` (half an hour)
|
||||
* - `renewable`: Should it be possible to extend the expiry date?; defaults to `true`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function createManually(array $options = [])
|
||||
{
|
||||
// only ever allow manual transmission mode
|
||||
// to prevent overwriting our "auto" session
|
||||
$options['mode'] = 'manual';
|
||||
/**
|
||||
* Creates a new empty session that is *not* automatically transmitted to the client
|
||||
* Useful for custom applications like a password reset link
|
||||
* Does *not* affect the automatic session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `startTime`: Time the session starts being valid (date string or timestamp); defaults to `now`
|
||||
* - `expiryTime`: Time the session expires (date string or timestamp); defaults to `+ 2 hours`
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); defaults to `1800` (half an hour)
|
||||
* - `renewable`: Should it be possible to extend the expiry date?; defaults to `true`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function createManually(array $options = [])
|
||||
{
|
||||
// only ever allow manual transmission mode
|
||||
// to prevent overwriting our "auto" session
|
||||
$options['mode'] = 'manual';
|
||||
|
||||
return $this->sessions->create($options);
|
||||
}
|
||||
return $this->sessions->create($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified Session object
|
||||
* @since 3.3.1
|
||||
*
|
||||
* @param string $token Session token, either including or without the key
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function getManually(string $token)
|
||||
{
|
||||
return $this->sessions->get($token, 'manual');
|
||||
}
|
||||
/**
|
||||
* Returns the specified Session object
|
||||
* @since 3.3.1
|
||||
*
|
||||
* @param string $token Session token, either including or without the key
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function getManually(string $token)
|
||||
{
|
||||
return $this->sessions->get($token, 'manual');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* If the `gcInterval` is configured, this is done automatically
|
||||
* when initializing the AutoSession class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$this->sessions->collectGarbage();
|
||||
}
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* If the `gcInterval` is configured, this is done automatically
|
||||
* when initializing the AutoSession class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$this->sessions->collectGarbage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Kirby\Exception\Exception;
|
|||
use Kirby\Exception\LogicException;
|
||||
use Kirby\Exception\NotFoundException;
|
||||
use Kirby\Filesystem\Dir;
|
||||
use Kirby\Filesystem\F;
|
||||
use Kirby\Toolkit\Str;
|
||||
|
||||
/**
|
||||
|
@ -18,464 +19,464 @@ use Kirby\Toolkit\Str;
|
|||
*/
|
||||
class FileSessionStore extends SessionStore
|
||||
{
|
||||
protected $path;
|
||||
protected $path;
|
||||
|
||||
// state of the session files
|
||||
protected $handles = [];
|
||||
protected $isLocked = [];
|
||||
// state of the session files
|
||||
protected $handles = [];
|
||||
protected $isLocked = [];
|
||||
|
||||
/**
|
||||
* Creates a new instance of the file session store
|
||||
*
|
||||
* @param string $path Path to the storage directory
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
// create the directory if it doesn't already exist
|
||||
Dir::make($path, true);
|
||||
/**
|
||||
* Creates a new instance of the file session store
|
||||
*
|
||||
* @param string $path Path to the storage directory
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
// create the directory if it doesn't already exist
|
||||
Dir::make($path, true);
|
||||
|
||||
// store the canonicalized path
|
||||
$this->path = realpath($path);
|
||||
// store the canonicalized path
|
||||
$this->path = realpath($path);
|
||||
|
||||
// make sure it is usable for storage
|
||||
if (!is_writable($this->path)) {
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.dirNotWritable',
|
||||
'data' => ['path' => $this->path],
|
||||
'fallback' => 'The session storage directory "' . $path . '" is not writable',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
}
|
||||
// make sure it is usable for storage
|
||||
if (!is_writable($this->path)) {
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.dirNotWritable',
|
||||
'data' => ['path' => $this->path],
|
||||
'fallback' => 'The session storage directory "' . $path . '" is not writable',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session ID with the given expiry time
|
||||
*
|
||||
* Needs to make sure that the session does not already exist
|
||||
* and needs to reserve it by locking it exclusively.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @return string Randomly generated session ID (without timestamp)
|
||||
*/
|
||||
public function createId(int $expiryTime): string
|
||||
{
|
||||
clearstatcache();
|
||||
do {
|
||||
// use helper from the abstract SessionStore class
|
||||
$id = static::generateId();
|
||||
/**
|
||||
* Creates a new session ID with the given expiry time
|
||||
*
|
||||
* Needs to make sure that the session does not already exist
|
||||
* and needs to reserve it by locking it exclusively.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @return string Randomly generated session ID (without timestamp)
|
||||
*/
|
||||
public function createId(int $expiryTime): string
|
||||
{
|
||||
clearstatcache();
|
||||
do {
|
||||
// use helper from the abstract SessionStore class
|
||||
$id = static::generateId();
|
||||
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
} while (file_exists($path));
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
} while (file_exists($path));
|
||||
|
||||
// reserve the file
|
||||
touch($path);
|
||||
$this->lock($expiryTime, $id);
|
||||
// reserve the file
|
||||
touch($path);
|
||||
$this->lock($expiryTime, $id);
|
||||
|
||||
// ensure that no other thread already wrote to the same file, otherwise try again
|
||||
// very unlikely scenario!
|
||||
$contents = $this->get($expiryTime, $id);
|
||||
if ($contents !== '') {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->unlock($expiryTime, $id);
|
||||
return $this->createId($expiryTime);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
// ensure that no other thread already wrote to the same file, otherwise try again
|
||||
// very unlikely scenario!
|
||||
$contents = $this->get($expiryTime, $id);
|
||||
if ($contents !== '') {
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->unlock($expiryTime, $id);
|
||||
return $this->createId($expiryTime);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given session exists
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return bool true: session exists,
|
||||
* false: session doesn't exist
|
||||
*/
|
||||
public function exists(int $expiryTime, string $id): bool
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
/**
|
||||
* Checks if the given session exists
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return bool true: session exists,
|
||||
* false: session doesn't exist
|
||||
*/
|
||||
public function exists(int $expiryTime, string $id): bool
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
|
||||
clearstatcache();
|
||||
return is_file($path) === true;
|
||||
}
|
||||
clearstatcache();
|
||||
return is_file($path) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the given session exclusively
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function lock(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
/**
|
||||
* Locks the given session exclusively
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function lock(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
|
||||
// check if the file is already locked
|
||||
if (isset($this->isLocked[$name])) {
|
||||
return;
|
||||
}
|
||||
// check if the file is already locked
|
||||
if (isset($this->isLocked[$name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// lock it exclusively
|
||||
$handle = $this->handle($name);
|
||||
$result = flock($handle, LOCK_EX);
|
||||
// lock it exclusively
|
||||
$handle = $this->handle($name);
|
||||
$result = flock($handle, LOCK_EX);
|
||||
|
||||
// make a note that the file is now locked
|
||||
if ($result === true) {
|
||||
$this->isLocked[$name] = true;
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
// make a note that the file is now locked
|
||||
if ($result === true) {
|
||||
$this->isLocked[$name] = true;
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all locks on the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function unlock(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
/**
|
||||
* Removes all locks on the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function unlock(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
|
||||
// check if the file is already unlocked or doesn't exist
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
return;
|
||||
} elseif ($this->exists($expiryTime, $id) === false) {
|
||||
unset($this->isLocked[$name]);
|
||||
return;
|
||||
}
|
||||
// check if the file is already unlocked or doesn't exist
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
return;
|
||||
} elseif ($this->exists($expiryTime, $id) === false) {
|
||||
unset($this->isLocked[$name]);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the exclusive lock
|
||||
$handle = $this->handle($name);
|
||||
$result = flock($handle, LOCK_UN);
|
||||
// remove the exclusive lock
|
||||
$handle = $this->handle($name);
|
||||
$result = flock($handle, LOCK_UN);
|
||||
|
||||
// make a note that the file is no longer locked
|
||||
if ($result === true) {
|
||||
unset($this->isLocked[$name]);
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
// make a note that the file is no longer locked
|
||||
if ($result === true) {
|
||||
unset($this->isLocked[$name]);
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stored session data of the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
public function get(int $expiryTime, string $id): string
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
$handle = $this->handle($name);
|
||||
/**
|
||||
* Returns the stored session data of the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
public function get(int $expiryTime, string $id): string
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
$handle = $this->handle($name);
|
||||
|
||||
// set read lock to prevent other threads from corrupting the data while we read it
|
||||
// only if we don't already have a write lock, which is even better
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
$result = flock($handle, LOCK_SH);
|
||||
// set read lock to prevent other threads from corrupting the data while we read it
|
||||
// only if we don't already have a write lock, which is even better
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
$result = flock($handle, LOCK_SH);
|
||||
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
clearstatcache();
|
||||
$filesize = filesize($path);
|
||||
if ($filesize > 0) {
|
||||
// always read the whole file
|
||||
rewind($handle);
|
||||
$string = fread($handle, $filesize);
|
||||
} else {
|
||||
// we don't need to read empty files
|
||||
$string = '';
|
||||
}
|
||||
clearstatcache();
|
||||
$filesize = filesize($path);
|
||||
if ($filesize > 0) {
|
||||
// always read the whole file
|
||||
rewind($handle);
|
||||
$string = fread($handle, $filesize);
|
||||
} else {
|
||||
// we don't need to read empty files
|
||||
$string = '';
|
||||
}
|
||||
|
||||
// remove the shared lock if we set one above
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
$result = flock($handle, LOCK_UN);
|
||||
// remove the shared lock if we set one above
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
$result = flock($handle, LOCK_UN);
|
||||
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores data to the given session
|
||||
*
|
||||
* Needs to make sure that the session exists.
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @param string $data Session data to write
|
||||
* @return void
|
||||
*/
|
||||
public function set(int $expiryTime, string $id, string $data)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$handle = $this->handle($name);
|
||||
/**
|
||||
* Stores data to the given session
|
||||
*
|
||||
* Needs to make sure that the session exists.
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @param string $data Session data to write
|
||||
* @return void
|
||||
*/
|
||||
public function set(int $expiryTime, string $id, string $data)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$handle = $this->handle($name);
|
||||
|
||||
// validate that we have an exclusive lock already
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.filestore.notLocked',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Cannot write to session "' . $name . '", because it is not locked',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
// validate that we have an exclusive lock already
|
||||
if (!isset($this->isLocked[$name])) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.filestore.notLocked',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Cannot write to session "' . $name . '", because it is not locked',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
|
||||
// delete all file contents first
|
||||
if (rewind($handle) !== true || ftruncate($handle, 0) !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
// delete all file contents first
|
||||
if (rewind($handle) !== true || ftruncate($handle, 0) !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// write the new contents
|
||||
$result = fwrite($handle, $data);
|
||||
if (!is_int($result) || $result === 0) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
// write the new contents
|
||||
$result = fwrite($handle, $data);
|
||||
if (!is_int($result) || $result === 0) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function destroy(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
/**
|
||||
* Deletes the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
public function destroy(int $expiryTime, string $id)
|
||||
{
|
||||
$name = $this->name($expiryTime, $id);
|
||||
$path = $this->path($name);
|
||||
|
||||
// close the file, otherwise we can't delete it on Windows;
|
||||
// deletion is *not* thread-safe because of this, but
|
||||
// resurrection of the file is prevented in $this->set() because of
|
||||
// the check in $this->handle() every time any method is called
|
||||
$this->unlock($expiryTime, $id);
|
||||
$this->closeHandle($name);
|
||||
// close the file, otherwise we can't delete it on Windows;
|
||||
// deletion is *not* thread-safe because of this, but
|
||||
// resurrection of the file is prevented in $this->set() because of
|
||||
// the check in $this->handle() every time any method is called
|
||||
$this->unlock($expiryTime, $id);
|
||||
$this->closeHandle($name);
|
||||
|
||||
// we don't need to delete files that don't exist anymore
|
||||
if ($this->exists($expiryTime, $id) === false) {
|
||||
return;
|
||||
}
|
||||
// we don't need to delete files that don't exist anymore
|
||||
if ($this->exists($expiryTime, $id) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// file still exists, delete it
|
||||
if (@unlink($path) !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
// file still exists, delete it
|
||||
if (@F::unlink($path) !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$iterator = new FilesystemIterator($this->path);
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$iterator = new FilesystemIterator($this->path);
|
||||
|
||||
$currentTime = time();
|
||||
foreach ($iterator as $file) {
|
||||
// make sure that the file is a session file
|
||||
// prevents deleting files like .gitignore or other unrelated files
|
||||
if (preg_match('/^[0-9]+\.[a-z0-9]+\.sess$/', $file->getFilename()) !== 1) {
|
||||
continue;
|
||||
}
|
||||
$currentTime = time();
|
||||
foreach ($iterator as $file) {
|
||||
// make sure that the file is a session file
|
||||
// prevents deleting files like .gitignore or other unrelated files
|
||||
if (preg_match('/^[0-9]+\.[a-z0-9]+\.sess$/', $file->getFilename()) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extract the data from the filename
|
||||
$name = $file->getBasename('.sess');
|
||||
$expiryTime = (int)Str::before($name, '.');
|
||||
$id = Str::after($name, '.');
|
||||
// extract the data from the filename
|
||||
$name = $file->getBasename('.sess');
|
||||
$expiryTime = (int)Str::before($name, '.');
|
||||
$id = Str::after($name, '.');
|
||||
|
||||
if ($expiryTime < $currentTime) {
|
||||
// the session has expired, delete it
|
||||
$this->destroy($expiryTime, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($expiryTime < $currentTime) {
|
||||
// the session has expired, delete it
|
||||
$this->destroy($expiryTime, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the open locks and file handles
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// unlock all locked files
|
||||
foreach ($this->isLocked as $name => $locked) {
|
||||
$expiryTime = (int)Str::before($name, '.');
|
||||
$id = Str::after($name, '.');
|
||||
/**
|
||||
* Cleans up the open locks and file handles
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// unlock all locked files
|
||||
foreach ($this->isLocked as $name => $locked) {
|
||||
$expiryTime = (int)Str::before($name, '.');
|
||||
$id = Str::after($name, '.');
|
||||
|
||||
$this->unlock($expiryTime, $id);
|
||||
}
|
||||
$this->unlock($expiryTime, $id);
|
||||
}
|
||||
|
||||
// close all file handles
|
||||
foreach ($this->handles as $name => $handle) {
|
||||
$this->closeHandle($name);
|
||||
}
|
||||
}
|
||||
// close all file handles
|
||||
foreach ($this->handles as $name => $handle) {
|
||||
$this->closeHandle($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the combined name based on expiry time and ID
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
protected function name(int $expiryTime, string $id): string
|
||||
{
|
||||
return $expiryTime . '.' . $id;
|
||||
}
|
||||
/**
|
||||
* Returns the combined name based on expiry time and ID
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
protected function name(int $expiryTime, string $id): string
|
||||
{
|
||||
return $expiryTime . '.' . $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to the session file
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return string
|
||||
*/
|
||||
protected function path(string $name): string
|
||||
{
|
||||
return $this->path . '/' . $name . '.sess';
|
||||
}
|
||||
/**
|
||||
* Returns the full path to the session file
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return string
|
||||
*/
|
||||
protected function path(string $name): string
|
||||
{
|
||||
return $this->path . '/' . $name . '.sess';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PHP file handle for a session
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return resource File handle
|
||||
*/
|
||||
protected function handle(string $name)
|
||||
{
|
||||
// always verify that the file still exists, even if we already have a handle;
|
||||
// ensures thread-safeness for recently deleted sessions, see $this->destroy()
|
||||
$path = $this->path($name);
|
||||
clearstatcache();
|
||||
if (!is_file($path)) {
|
||||
throw new NotFoundException([
|
||||
'key' => 'session.filestore.notFound',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Session file "' . $name . '" does not exist',
|
||||
'translate' => false,
|
||||
'httpCode' => 404
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Returns a PHP file handle for a session
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return resource File handle
|
||||
*/
|
||||
protected function handle(string $name)
|
||||
{
|
||||
// always verify that the file still exists, even if we already have a handle;
|
||||
// ensures thread-safeness for recently deleted sessions, see $this->destroy()
|
||||
$path = $this->path($name);
|
||||
clearstatcache();
|
||||
if (!is_file($path)) {
|
||||
throw new NotFoundException([
|
||||
'key' => 'session.filestore.notFound',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Session file "' . $name . '" does not exist',
|
||||
'translate' => false,
|
||||
'httpCode' => 404
|
||||
]);
|
||||
}
|
||||
|
||||
// return from cache
|
||||
if (isset($this->handles[$name])) {
|
||||
return $this->handles[$name];
|
||||
}
|
||||
// return from cache
|
||||
if (isset($this->handles[$name])) {
|
||||
return $this->handles[$name];
|
||||
}
|
||||
|
||||
// open a new handle
|
||||
$handle = @fopen($path, 'r+b');
|
||||
if (!is_resource($handle)) {
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.notOpened',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Session file "' . $name . '" could not be opened',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
// open a new handle
|
||||
$handle = @fopen($path, 'r+b');
|
||||
if (!is_resource($handle)) {
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.notOpened',
|
||||
'data' => ['name' => $name],
|
||||
'fallback' => 'Session file "' . $name . '" could not be opened',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->handles[$name] = $handle;
|
||||
}
|
||||
return $this->handles[$name] = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an open file handle
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return void
|
||||
*/
|
||||
protected function closeHandle(string $name)
|
||||
{
|
||||
if (!isset($this->handles[$name])) {
|
||||
return;
|
||||
}
|
||||
$handle = $this->handles[$name];
|
||||
/**
|
||||
* Closes an open file handle
|
||||
*
|
||||
* @param string $name Combined name
|
||||
* @return void
|
||||
*/
|
||||
protected function closeHandle(string $name)
|
||||
{
|
||||
if (!isset($this->handles[$name])) {
|
||||
return;
|
||||
}
|
||||
$handle = $this->handles[$name];
|
||||
|
||||
unset($this->handles[$name]);
|
||||
$result = fclose($handle);
|
||||
unset($this->handles[$name]);
|
||||
$result = fclose($handle);
|
||||
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
if ($result !== true) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new Exception([
|
||||
'key' => 'session.filestore.unexpectedFilesystemError',
|
||||
'fallback' => 'Unexpected file system error',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,238 +18,238 @@ use Kirby\Exception\LogicException;
|
|||
*/
|
||||
class SessionData
|
||||
{
|
||||
protected $session;
|
||||
protected $data;
|
||||
protected $session;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Creates a new SessionData instance
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param \Kirby\Session\Session $session Session object this data belongs to
|
||||
* @param array $data Currently stored session data
|
||||
*/
|
||||
public function __construct(Session $session, array $data)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->data = $data;
|
||||
}
|
||||
/**
|
||||
* Creates a new SessionData instance
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param \Kirby\Session\Session $session Session object this data belongs to
|
||||
* @param array $data Currently stored session data
|
||||
*/
|
||||
public function __construct(Session $session, array $data)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one or multiple session values by key
|
||||
*
|
||||
* @param string|array $key The key to define or a key-value array with multiple values
|
||||
* @param mixed $value The value for the passed key (only if one $key is passed)
|
||||
* @return void
|
||||
*/
|
||||
public function set($key, $value = null)
|
||||
{
|
||||
$this->session->ensureToken();
|
||||
$this->session->prepareForWriting();
|
||||
/**
|
||||
* Sets one or multiple session values by key
|
||||
*
|
||||
* @param string|array $key The key to define or a key-value array with multiple values
|
||||
* @param mixed $value The value for the passed key (only if one $key is passed)
|
||||
* @return void
|
||||
*/
|
||||
public function set($key, $value = null)
|
||||
{
|
||||
$this->session->ensureToken();
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
if (is_string($key)) {
|
||||
$this->data[$key] = $value;
|
||||
} elseif (is_array($key)) {
|
||||
$this->data = array_merge($this->data, $key);
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::set', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (is_string($key)) {
|
||||
$this->data[$key] = $value;
|
||||
} elseif (is_array($key)) {
|
||||
$this->data = array_merge($this->data, $key);
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::set', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments one or multiple session values by a specified amount
|
||||
*
|
||||
* @param string|array $key The key to increment or an array with multiple keys
|
||||
* @param int $by Increment by which amount?
|
||||
* @param int $max Maximum amount (value is not incremented further)
|
||||
* @return void
|
||||
*/
|
||||
public function increment($key, int $by = 1, $max = null)
|
||||
{
|
||||
if ($max !== null && !is_int($max)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::increment', 'argument' => 'max'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Increments one or multiple session values by a specified amount
|
||||
*
|
||||
* @param string|array $key The key to increment or an array with multiple keys
|
||||
* @param int $by Increment by which amount?
|
||||
* @param int $max Maximum amount (value is not incremented further)
|
||||
* @return void
|
||||
*/
|
||||
public function increment($key, int $by = 1, $max = null)
|
||||
{
|
||||
if ($max !== null && !is_int($max)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::increment', 'argument' => 'max'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
if (is_string($key)) {
|
||||
// make sure we have the correct values before getting
|
||||
$this->session->prepareForWriting();
|
||||
if (is_string($key)) {
|
||||
// make sure we have the correct values before getting
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
$value = $this->get($key, 0);
|
||||
$value = $this->get($key, 0);
|
||||
|
||||
if (!is_int($value)) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.data.increment.nonInt',
|
||||
'data' => ['key' => $key],
|
||||
'fallback' => 'Session value "' . $key . '" is not an integer and cannot be incremented',
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
if (!is_int($value)) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.data.increment.nonInt',
|
||||
'data' => ['key' => $key],
|
||||
'fallback' => 'Session value "' . $key . '" is not an integer and cannot be incremented',
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
// increment the value, but ensure $max constraint
|
||||
if (is_int($max) && $value + $by > $max) {
|
||||
// set the value to $max
|
||||
// but not if the current $value is already larger than $max
|
||||
$value = max($value, $max);
|
||||
} else {
|
||||
$value += $by;
|
||||
}
|
||||
// increment the value, but ensure $max constraint
|
||||
if (is_int($max) && $value + $by > $max) {
|
||||
// set the value to $max
|
||||
// but not if the current $value is already larger than $max
|
||||
$value = max($value, $max);
|
||||
} else {
|
||||
$value += $by;
|
||||
}
|
||||
|
||||
$this->set($key, $value);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
$this->increment($k, $by, $max);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::increment', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->set($key, $value);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
$this->increment($k, $by, $max);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::increment', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements one or multiple session values by a specified amount
|
||||
*
|
||||
* @param string|array $key The key to decrement or an array with multiple keys
|
||||
* @param int $by Decrement by which amount?
|
||||
* @param int $min Minimum amount (value is not decremented further)
|
||||
* @return void
|
||||
*/
|
||||
public function decrement($key, int $by = 1, $min = null)
|
||||
{
|
||||
if ($min !== null && !is_int($min)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::decrement', 'argument' => 'min'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Decrements one or multiple session values by a specified amount
|
||||
*
|
||||
* @param string|array $key The key to decrement or an array with multiple keys
|
||||
* @param int $by Decrement by which amount?
|
||||
* @param int $min Minimum amount (value is not decremented further)
|
||||
* @return void
|
||||
*/
|
||||
public function decrement($key, int $by = 1, $min = null)
|
||||
{
|
||||
if ($min !== null && !is_int($min)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::decrement', 'argument' => 'min'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
if (is_string($key)) {
|
||||
// make sure we have the correct values before getting
|
||||
$this->session->prepareForWriting();
|
||||
if (is_string($key)) {
|
||||
// make sure we have the correct values before getting
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
$value = $this->get($key, 0);
|
||||
$value = $this->get($key, 0);
|
||||
|
||||
if (!is_int($value)) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.data.decrement.nonInt',
|
||||
'data' => ['key' => $key],
|
||||
'fallback' => 'Session value "' . $key . '" is not an integer and cannot be decremented',
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
if (!is_int($value)) {
|
||||
throw new LogicException([
|
||||
'key' => 'session.data.decrement.nonInt',
|
||||
'data' => ['key' => $key],
|
||||
'fallback' => 'Session value "' . $key . '" is not an integer and cannot be decremented',
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
// decrement the value, but ensure $min constraint
|
||||
if (is_int($min) && $value - $by < $min) {
|
||||
// set the value to $min
|
||||
// but not if the current $value is already smaller than $min
|
||||
$value = min($value, $min);
|
||||
} else {
|
||||
$value -= $by;
|
||||
}
|
||||
// decrement the value, but ensure $min constraint
|
||||
if (is_int($min) && $value - $by < $min) {
|
||||
// set the value to $min
|
||||
// but not if the current $value is already smaller than $min
|
||||
$value = min($value, $min);
|
||||
} else {
|
||||
$value -= $by;
|
||||
}
|
||||
|
||||
$this->set($key, $value);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
$this->decrement($k, $by, $min);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::decrement', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->set($key, $value);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
$this->decrement($k, $by, $min);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::decrement', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one or all session values by key
|
||||
*
|
||||
* @param string|null $key The key to get or null for the entire data array
|
||||
* @param mixed $default Optional default value to return if the key is not defined
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key = null, $default = null)
|
||||
{
|
||||
if (is_string($key)) {
|
||||
return $this->data[$key] ?? $default;
|
||||
} elseif ($key === null) {
|
||||
return $this->data;
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::get', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns one or all session values by key
|
||||
*
|
||||
* @param string|null $key The key to get or null for the entire data array
|
||||
* @param mixed $default Optional default value to return if the key is not defined
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key = null, $default = null)
|
||||
{
|
||||
if (is_string($key)) {
|
||||
return $this->data[$key] ?? $default;
|
||||
} elseif ($key === null) {
|
||||
return $this->data;
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::get', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value and removes it afterwards
|
||||
*
|
||||
* @param string $key The key to get
|
||||
* @param mixed $default Optional default value to return if the key is not defined
|
||||
* @return mixed
|
||||
*/
|
||||
public function pull(string $key, $default = null)
|
||||
{
|
||||
// make sure we have the correct value before getting
|
||||
// we do this here (but not in get) as we need to write anyway
|
||||
$this->session->prepareForWriting();
|
||||
/**
|
||||
* Retrieves a value and removes it afterwards
|
||||
*
|
||||
* @param string $key The key to get
|
||||
* @param mixed $default Optional default value to return if the key is not defined
|
||||
* @return mixed
|
||||
*/
|
||||
public function pull(string $key, $default = null)
|
||||
{
|
||||
// make sure we have the correct value before getting
|
||||
// we do this here (but not in get) as we need to write anyway
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
$value = $this->get($key, $default);
|
||||
$this->remove($key);
|
||||
return $value;
|
||||
}
|
||||
$value = $this->get($key, $default);
|
||||
$this->remove($key);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one or multiple session values by key
|
||||
*
|
||||
* @param string|array $key The key to remove or an array with multiple keys
|
||||
* @return void
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->session->prepareForWriting();
|
||||
/**
|
||||
* Removes one or multiple session values by key
|
||||
*
|
||||
* @param string|array $key The key to remove or an array with multiple keys
|
||||
* @return void
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
if (is_string($key)) {
|
||||
unset($this->data[$key]);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
unset($this->data[$k]);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::remove', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (is_string($key)) {
|
||||
unset($this->data[$key]);
|
||||
} elseif (is_array($key)) {
|
||||
foreach ($key as $k) {
|
||||
unset($this->data[$k]);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'SessionData::remove', 'argument' => 'key'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all session data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->session->prepareForWriting();
|
||||
/**
|
||||
* Clears all session data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->session->prepareForWriting();
|
||||
|
||||
$this->data = [];
|
||||
}
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the data array with the current session data
|
||||
* Only used internally
|
||||
*
|
||||
* @param array $data Currently stored session data
|
||||
* @return void
|
||||
*/
|
||||
public function reload(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
/**
|
||||
* Reloads the data array with the current session data
|
||||
* Only used internally
|
||||
*
|
||||
* @param array $data Currently stored session data
|
||||
* @return void
|
||||
*/
|
||||
public function reload(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,100 +11,100 @@ namespace Kirby\Session;
|
|||
*/
|
||||
abstract class SessionStore
|
||||
{
|
||||
/**
|
||||
* Creates a new session ID with the given expiry time
|
||||
*
|
||||
* Needs to make sure that the session does not already exist
|
||||
* and needs to reserve it by locking it exclusively.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @return string Randomly generated session ID (without timestamp)
|
||||
*/
|
||||
abstract public function createId(int $expiryTime): string;
|
||||
/**
|
||||
* Creates a new session ID with the given expiry time
|
||||
*
|
||||
* Needs to make sure that the session does not already exist
|
||||
* and needs to reserve it by locking it exclusively.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @return string Randomly generated session ID (without timestamp)
|
||||
*/
|
||||
abstract public function createId(int $expiryTime): string;
|
||||
|
||||
/**
|
||||
* Checks if the given session exists
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return bool true: session exists,
|
||||
* false: session doesn't exist
|
||||
*/
|
||||
abstract public function exists(int $expiryTime, string $id): bool;
|
||||
/**
|
||||
* Checks if the given session exists
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return bool true: session exists,
|
||||
* false: session doesn't exist
|
||||
*/
|
||||
abstract public function exists(int $expiryTime, string $id): bool;
|
||||
|
||||
/**
|
||||
* Locks the given session exclusively
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function lock(int $expiryTime, string $id);
|
||||
/**
|
||||
* Locks the given session exclusively
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function lock(int $expiryTime, string $id);
|
||||
|
||||
/**
|
||||
* Removes all locks on the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function unlock(int $expiryTime, string $id);
|
||||
/**
|
||||
* Removes all locks on the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function unlock(int $expiryTime, string $id);
|
||||
|
||||
/**
|
||||
* Returns the stored session data of the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get(int $expiryTime, string $id): string;
|
||||
/**
|
||||
* Returns the stored session data of the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get(int $expiryTime, string $id): string;
|
||||
|
||||
/**
|
||||
* Stores data to the given session
|
||||
*
|
||||
* Needs to make sure that the session exists.
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @param string $data Session data to write
|
||||
* @return void
|
||||
*/
|
||||
abstract public function set(int $expiryTime, string $id, string $data);
|
||||
/**
|
||||
* Stores data to the given session
|
||||
*
|
||||
* Needs to make sure that the session exists.
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @param string $data Session data to write
|
||||
* @return void
|
||||
*/
|
||||
abstract public function set(int $expiryTime, string $id, string $data);
|
||||
|
||||
/**
|
||||
* Deletes the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function destroy(int $expiryTime, string $id);
|
||||
/**
|
||||
* Deletes the given session
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @param int $expiryTime Timestamp
|
||||
* @param string $id Session ID
|
||||
* @return void
|
||||
*/
|
||||
abstract public function destroy(int $expiryTime, string $id);
|
||||
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function collectGarbage();
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* Needs to throw an Exception on error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function collectGarbage();
|
||||
|
||||
/**
|
||||
* Securely generates a random session ID
|
||||
*
|
||||
* @return string Random hex string with 20 bytes
|
||||
*/
|
||||
protected static function generateId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(10));
|
||||
}
|
||||
/**
|
||||
* Securely generates a random session ID
|
||||
*
|
||||
* @return string Random hex string with 20 bytes
|
||||
*/
|
||||
protected static function generateId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(10));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,267 +21,267 @@ use Throwable;
|
|||
*/
|
||||
class Sessions
|
||||
{
|
||||
protected $store;
|
||||
protected $mode;
|
||||
protected $cookieName;
|
||||
protected $store;
|
||||
protected $mode;
|
||||
protected $cookieName;
|
||||
|
||||
protected $cache = [];
|
||||
protected $cache = [];
|
||||
|
||||
/**
|
||||
* Creates a new Sessions instance
|
||||
*
|
||||
* @param \Kirby\Session\SessionStore|string $store SessionStore object or a path to the storage directory (uses the FileSessionStore)
|
||||
* @param array $options Optional additional options:
|
||||
* - `mode`: Default token transmission mode (cookie, header or manual); defaults to `cookie`
|
||||
* - `cookieName`: Name to use for the session cookie; defaults to `kirby_session`
|
||||
* - `gcInterval`: How often should the garbage collector be run?; integer or `false` for never; defaults to `100`
|
||||
*/
|
||||
public function __construct($store, array $options = [])
|
||||
{
|
||||
if (is_string($store)) {
|
||||
$this->store = new FileSessionStore($store);
|
||||
} elseif (is_a($store, 'Kirby\Session\SessionStore') === true) {
|
||||
$this->store = $store;
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => 'store'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Creates a new Sessions instance
|
||||
*
|
||||
* @param \Kirby\Session\SessionStore|string $store SessionStore object or a path to the storage directory (uses the FileSessionStore)
|
||||
* @param array $options Optional additional options:
|
||||
* - `mode`: Default token transmission mode (cookie, header or manual); defaults to `cookie`
|
||||
* - `cookieName`: Name to use for the session cookie; defaults to `kirby_session`
|
||||
* - `gcInterval`: How often should the garbage collector be run?; integer or `false` for never; defaults to `100`
|
||||
*/
|
||||
public function __construct($store, array $options = [])
|
||||
{
|
||||
if (is_string($store)) {
|
||||
$this->store = new FileSessionStore($store);
|
||||
} elseif (is_a($store, 'Kirby\Session\SessionStore') === true) {
|
||||
$this->store = $store;
|
||||
} else {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => 'store'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
$this->mode = $options['mode'] ?? 'cookie';
|
||||
$this->cookieName = $options['cookieName'] ?? 'kirby_session';
|
||||
$gcInterval = $options['gcInterval'] ?? 100;
|
||||
$this->mode = $options['mode'] ?? 'cookie';
|
||||
$this->cookieName = $options['cookieName'] ?? 'kirby_session';
|
||||
$gcInterval = $options['gcInterval'] ?? 100;
|
||||
|
||||
// validate options
|
||||
if (!in_array($this->mode, ['cookie', 'header', 'manual'])) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'mode\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
if (!is_string($this->cookieName)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'cookieName\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
// validate options
|
||||
if (!in_array($this->mode, ['cookie', 'header', 'manual'])) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'mode\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
if (!is_string($this->cookieName)) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'cookieName\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
|
||||
// trigger automatic garbage collection with the given probability
|
||||
if (is_int($gcInterval) && $gcInterval > 0) {
|
||||
// convert the interval into a probability between 0 and 1
|
||||
$gcProbability = 1 / $gcInterval;
|
||||
// trigger automatic garbage collection with the given probability
|
||||
if (is_int($gcInterval) && $gcInterval > 0) {
|
||||
// convert the interval into a probability between 0 and 1
|
||||
$gcProbability = 1 / $gcInterval;
|
||||
|
||||
// generate a random number
|
||||
$random = mt_rand(1, 10000);
|
||||
// generate a random number
|
||||
$random = mt_rand(1, 10000);
|
||||
|
||||
// $random will be below or equal $gcProbability * 10000 with a probability of $gcProbability
|
||||
if ($random <= $gcProbability * 10000) {
|
||||
$this->collectGarbage();
|
||||
}
|
||||
} elseif ($gcInterval !== false) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'gcInterval\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
// $random will be below or equal $gcProbability * 10000 with a probability of $gcProbability
|
||||
if ($random <= $gcProbability * 10000) {
|
||||
$this->collectGarbage();
|
||||
}
|
||||
} elseif ($gcInterval !== false) {
|
||||
throw new InvalidArgumentException([
|
||||
'data' => ['method' => 'Sessions::__construct', 'argument' => '$options[\'gcInterval\']'],
|
||||
'translate' => false
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `mode`: Token transmission mode (cookie or manual); defaults to default mode of the Sessions instance
|
||||
* - `startTime`: Time the session starts being valid (date string or timestamp); defaults to `now`
|
||||
* - `expiryTime`: Time the session expires (date string or timestamp); defaults to `+ 2 hours`
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); defaults to `1800` (half an hour)
|
||||
* - `renewable`: Should it be possible to extend the expiry date?; defaults to `true`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function create(array $options = [])
|
||||
{
|
||||
// fall back to default mode
|
||||
if (!isset($options['mode'])) {
|
||||
$options['mode'] = $this->mode;
|
||||
}
|
||||
/**
|
||||
* Creates a new empty session
|
||||
*
|
||||
* @param array $options Optional additional options:
|
||||
* - `mode`: Token transmission mode (cookie or manual); defaults to default mode of the Sessions instance
|
||||
* - `startTime`: Time the session starts being valid (date string or timestamp); defaults to `now`
|
||||
* - `expiryTime`: Time the session expires (date string or timestamp); defaults to `+ 2 hours`
|
||||
* - `timeout`: Activity timeout in seconds (integer or false for none); defaults to `1800` (half an hour)
|
||||
* - `renewable`: Should it be possible to extend the expiry date?; defaults to `true`
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function create(array $options = [])
|
||||
{
|
||||
// fall back to default mode
|
||||
if (!isset($options['mode'])) {
|
||||
$options['mode'] = $this->mode;
|
||||
}
|
||||
|
||||
return new Session($this, null, $options);
|
||||
}
|
||||
return new Session($this, null, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified Session object
|
||||
*
|
||||
* @param string $token Session token, either including or without the key
|
||||
* @param string $mode Optional transmission mode override
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function get(string $token, string $mode = null)
|
||||
{
|
||||
if (isset($this->cache[$token])) {
|
||||
return $this->cache[$token];
|
||||
}
|
||||
/**
|
||||
* Returns the specified Session object
|
||||
*
|
||||
* @param string $token Session token, either including or without the key
|
||||
* @param string $mode Optional transmission mode override
|
||||
* @return \Kirby\Session\Session
|
||||
*/
|
||||
public function get(string $token, string $mode = null)
|
||||
{
|
||||
if (isset($this->cache[$token])) {
|
||||
return $this->cache[$token];
|
||||
}
|
||||
|
||||
return $this->cache[$token] = new Session($this, $token, ['mode' => $mode ?? $this->mode]);
|
||||
}
|
||||
return $this->cache[$token] = new Session($this, $token, ['mode' => $mode ?? $this->mode]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current session based on the configured token transmission mode:
|
||||
* - In `cookie` mode: Gets the session from the cookie
|
||||
* - In `header` mode: Gets the session from the `Authorization` request header
|
||||
* - In `manual` mode: Fails and throws an Exception
|
||||
*
|
||||
* @return \Kirby\Session\Session|null Either the current session or null in case there isn't one
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$token = null;
|
||||
switch ($this->mode) {
|
||||
case 'cookie':
|
||||
$token = $this->tokenFromCookie();
|
||||
break;
|
||||
case 'header':
|
||||
$token = $this->tokenFromHeader();
|
||||
break;
|
||||
case 'manual':
|
||||
throw new LogicException([
|
||||
'key' => 'session.sessions.manualMode',
|
||||
'fallback' => 'Cannot automatically get current session in manual mode',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
default:
|
||||
// unexpected error that shouldn't occur
|
||||
throw new Exception(['translate' => false]); // @codeCoverageIgnore
|
||||
}
|
||||
/**
|
||||
* Returns the current session based on the configured token transmission mode:
|
||||
* - In `cookie` mode: Gets the session from the cookie
|
||||
* - In `header` mode: Gets the session from the `Authorization` request header
|
||||
* - In `manual` mode: Fails and throws an Exception
|
||||
*
|
||||
* @return \Kirby\Session\Session|null Either the current session or null in case there isn't one
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$token = null;
|
||||
switch ($this->mode) {
|
||||
case 'cookie':
|
||||
$token = $this->tokenFromCookie();
|
||||
break;
|
||||
case 'header':
|
||||
$token = $this->tokenFromHeader();
|
||||
break;
|
||||
case 'manual':
|
||||
throw new LogicException([
|
||||
'key' => 'session.sessions.manualMode',
|
||||
'fallback' => 'Cannot automatically get current session in manual mode',
|
||||
'translate' => false,
|
||||
'httpCode' => 500
|
||||
]);
|
||||
default:
|
||||
// unexpected error that shouldn't occur
|
||||
throw new Exception(['translate' => false]); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// no token was found, no session
|
||||
if (!is_string($token)) {
|
||||
return null;
|
||||
}
|
||||
// no token was found, no session
|
||||
if (!is_string($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// token was found, try to get the session
|
||||
try {
|
||||
return $this->get($token);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// token was found, try to get the session
|
||||
try {
|
||||
return $this->get($token);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current session using the following detection order without using the configured mode:
|
||||
* - Tries to get the session from the `Authorization` request header
|
||||
* - Tries to get the session from the cookie
|
||||
* - Otherwise returns null
|
||||
*
|
||||
* @return \Kirby\Session\Session|null Either the current session or null in case there isn't one
|
||||
*/
|
||||
public function currentDetected()
|
||||
{
|
||||
$tokenFromHeader = $this->tokenFromHeader();
|
||||
$tokenFromCookie = $this->tokenFromCookie();
|
||||
/**
|
||||
* Returns the current session using the following detection order without using the configured mode:
|
||||
* - Tries to get the session from the `Authorization` request header
|
||||
* - Tries to get the session from the cookie
|
||||
* - Otherwise returns null
|
||||
*
|
||||
* @return \Kirby\Session\Session|null Either the current session or null in case there isn't one
|
||||
*/
|
||||
public function currentDetected()
|
||||
{
|
||||
$tokenFromHeader = $this->tokenFromHeader();
|
||||
$tokenFromCookie = $this->tokenFromCookie();
|
||||
|
||||
// prefer header token over cookie token
|
||||
$token = $tokenFromHeader ?? $tokenFromCookie;
|
||||
// prefer header token over cookie token
|
||||
$token = $tokenFromHeader ?? $tokenFromCookie;
|
||||
|
||||
// no token was found, no session
|
||||
if (!is_string($token)) {
|
||||
return null;
|
||||
}
|
||||
// no token was found, no session
|
||||
if (!is_string($token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// token was found, try to get the session
|
||||
try {
|
||||
$mode = (is_string($tokenFromHeader)) ? 'header' : 'cookie';
|
||||
return $this->get($token, $mode);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// token was found, try to get the session
|
||||
try {
|
||||
$mode = (is_string($tokenFromHeader)) ? 'header' : 'cookie';
|
||||
return $this->get($token, $mode);
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the session store instance
|
||||
* Used internally
|
||||
*
|
||||
* @return \Kirby\Session\SessionStore
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
/**
|
||||
* Getter for the session store instance
|
||||
* Used internally
|
||||
*
|
||||
* @return \Kirby\Session\SessionStore
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the cookie name
|
||||
* Used internally
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cookieName(): string
|
||||
{
|
||||
return $this->cookieName;
|
||||
}
|
||||
/**
|
||||
* Getter for the cookie name
|
||||
* Used internally
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cookieName(): string
|
||||
{
|
||||
return $this->cookieName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* If the `gcInterval` is configured, this is done automatically
|
||||
* on init of the Sessions object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$this->store()->collectGarbage();
|
||||
}
|
||||
/**
|
||||
* Deletes all expired sessions
|
||||
*
|
||||
* If the `gcInterval` is configured, this is done automatically
|
||||
* on init of the Sessions object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
$this->store()->collectGarbage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the instance cache with a newly created
|
||||
* session or a session with a regenerated token
|
||||
*
|
||||
* @internal
|
||||
* @param \Kirby\Session\Session $session Session instance to push to the cache
|
||||
*/
|
||||
public function updateCache(Session $session)
|
||||
{
|
||||
$this->cache[$session->token()] = $session;
|
||||
}
|
||||
/**
|
||||
* Updates the instance cache with a newly created
|
||||
* session or a session with a regenerated token
|
||||
*
|
||||
* @internal
|
||||
* @param \Kirby\Session\Session $session Session instance to push to the cache
|
||||
*/
|
||||
public function updateCache(Session $session)
|
||||
{
|
||||
$this->cache[$session->token()] = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth token from the cookie
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function tokenFromCookie()
|
||||
{
|
||||
$value = Cookie::get($this->cookieName());
|
||||
/**
|
||||
* Returns the auth token from the cookie
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function tokenFromCookie()
|
||||
{
|
||||
$value = Cookie::get($this->cookieName());
|
||||
|
||||
if (is_string($value)) {
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (is_string($value)) {
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth token from the Authorization header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function tokenFromHeader()
|
||||
{
|
||||
$request = new Request();
|
||||
$headers = $request->headers();
|
||||
/**
|
||||
* Returns the auth token from the Authorization header
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function tokenFromHeader()
|
||||
{
|
||||
$request = new Request();
|
||||
$headers = $request->headers();
|
||||
|
||||
// check if the header exists at all
|
||||
if (!isset($headers['Authorization'])) {
|
||||
return null;
|
||||
}
|
||||
// check if the header exists at all
|
||||
if (!isset($headers['Authorization'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if the header uses the "Session" scheme
|
||||
$header = $headers['Authorization'];
|
||||
if (Str::startsWith($header, 'Session ', true) !== true) {
|
||||
return null;
|
||||
}
|
||||
// check if the header uses the "Session" scheme
|
||||
$header = $headers['Authorization'];
|
||||
if (Str::startsWith($header, 'Session ', true) !== true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return the part after the scheme
|
||||
return substr($header, 8);
|
||||
}
|
||||
// return the part after the scheme
|
||||
return substr($header, 8);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue