OpenXE/classes/Components/Http/Session/Session.php
2021-05-21 08:49:41 +02:00

316 lines
8.3 KiB
PHP

<?php
namespace Xentral\Components\Http\Session;
use Xentral\Components\Http\Exception\InvalidArgumentException;
use Xentral\Components\Http\Exception\SessionSegmentException;
class Session
{
/** @var string FLASH_SEGMENTKEY */
const FLASH_SEGMENTKEY = 'flash_messages';
/** @var string CSRF_SEGMENTKEY */
const CSRF_SEGMENTKEY = 'csrf_tokens';
/** @var array $data */
protected $data;
/** @var Segment[] $segments */
protected $segments;
/** @var FlashMessageCollection $flashData */
protected $flashMessages;
/** @var CsrfTokenManager $csrfTokens */
protected $csrfTokens;
/**
* @param array $data
*/
public function __construct($data = [])
{
$this->data = (array)$data;
$this->segments = [];
$this->flashMessages = $this->createFlashMessageCollection();
$this->csrfTokens = $this->createCsrfTokenManager();
}
/**
* Clears all Session data and flash messages
*
* @return void
*/
public function clearAll()
{
$this->data = [];
$this->segments = [];
$this->flashMessages = new FlashMessageCollection();
}
/**
* Gets a value with a specific key from the current Segment
*
* @param string $segment
* @param string $key
* @param null $default
* @param bool $clear true=remove entry from the session
*
* @return mixed|null
*/
public function getValue($segment, $key, $default = null, $clear = false)
{
return $this->getSegment($segment)->getValue($key, $default, $clear);
}
/**
* Makes an entry to the current Segment
*
* @param string $segment
* @param string $key
* @param string|int|float|array $value
*
* @throws InvalidArgumentException
*
* @return void
*/
public function setValue($segment, $key, $value)
{
$this->getSegment($segment)->setValue($key, $value);
}
/**
* Removes a single Entry from the current segment
*
* @param string $segment
* @param string $key
*
* @throws InvalidArgumentException
*
* @return void
*/
public function removeValue($segment, $key)
{
$this->getSegment($segment)->removeValue($key);
}
/**
* Gets a segment object by it's name
*
* @param string $name
*
* @throws InvalidArgumentException
*
* @return Segment
*/
public function getSegment($name = '')
{
if ($name === self::FLASH_SEGMENTKEY || $name === self::CSRF_SEGMENTKEY) {
throw new SessionSegmentException(
sprintf('"%s" is a reserved segment name.', self::FLASH_SEGMENTKEY)
);
}
$segmentId = $this->getSegmentKey($name);
if (array_key_exists($segmentId, $this->segments)) {
return $this->segments[$segmentId];
}
$data = [];
if (array_key_exists($segmentId, $this->data)) {
$data = $this->data[$segmentId];
}
$segment = new Segment($this, $name, $data);
$this->segments[$segmentId] = $segment;
return $segment;
}
/**
* Dumps the whole session into specific target variable.
*
* @param mixed $targetVariable
*/
public function dumpSession(&$targetVariable)
{
$this->mergeSession();
$targetVariable = $this->data;
}
/**
* @return array
*/
public function __debugInfo()
{
$this->dumpSession($dump);
$this->csrfTokens->dumpTokens($tokendump);
$tokendump = array_keys($tokendump);
return [
'data' => $dump,
'tokens' => $tokendump
];
}
/**
* Adds new flash message to specific segment.
*
* @param string $segment if empty: default segment will be used
* @param string $message
* @param string $type
* @param int $priority
*
* @return void
*/
public function addFlashMessage($segment, $message, $type = FlashMessageData::FLASHTYPE_DEFAULT, $priority = 0)
{
$flash = new FlashMessageData($message, $type, $segment, $priority);
$flashData = $flash->toSessionArray();
$key = (string)$this->getSegmentKey(self::FLASH_SEGMENTKEY);
$this->data[$key][] = $flashData;
}
/**
* Gets flash message(s) by specific filter conditions.
*
* The flash message will be cleared from the session after retrieving
*
* @param string|null $segment filter for segment name
* @param string|null $type filter for message type
*
* @return FlashMessageData[] flash messages sorted by priority
*/
public function getFlashMessages($segment = null, $type = null)
{
return $this->flashMessages->getMessages($segment, $type);
}
/**
* Creates a CSRF Token and stores it in the Session
*
* @param string $tokenKey
*
* @throws InvalidArgumentException
*
* @return string
*/
public function createCsrfToken($tokenKey)
{
return $this->csrfTokens->createToken($tokenKey);
}
/**
* Returns true if specified Token is valid
*
* @param string $tokenKey
* @param string $tokenValue
* @param bool $remove true=remove token from session to mitigate second use
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function isCsrfTokenValid($tokenKey, $tokenValue, $remove = false)
{
return $this->csrfTokens->isTokenValid($tokenKey, $tokenValue, $remove);
}
/**
* @param $segmentName
*
* @throws InvalidArgumentException
*
* @return string
*/
private function getSegmentKey($segmentName)
{
$this->ensureSegmentNameFormat($segmentName);
return sprintf('segment_%s', $segmentName);
}
/**
* Merge all Segments and Flashmessages and CsrfTokens into the session array.
*
* @return void
*/
private function mergeSession()
{
foreach ($this->segments as $key => $segment) {
$segmentData = $segment->getAll();
if (count($segmentData) > 0) {
$this->data[$key] = $segmentData;
}
}
$flashKey = $this->getSegmentKey(self::FLASH_SEGMENTKEY);
$newFlashes = [];
if (array_key_exists($flashKey, $this->data)) {
$newFlashes = $this->data[$flashKey];
}
$oldFlashes = $this->flashMessages->toSessionArray();
$this->flashMessages = new FlashMessageCollection();
$allFlashes = array_merge($oldFlashes, $newFlashes);
if (count($allFlashes) > 0) {
$this->data[$flashKey] = $allFlashes;
} else {
unset($this->data[$flashKey]);
}
$this->csrfTokens->dumpTokens($tokens);
$tokenSegmentKey = $this->getSegmentKey(self::CSRF_SEGMENTKEY);
if (is_array($tokens) && count($tokens) > 0) {
$this->data[$tokenSegmentKey] = $tokens;
} else {
unset($this->data[$tokenSegmentKey]);
}
}
/**
* @return FlashMessageCollection
*/
private function createFlashMessageCollection()
{
$key = $this->getSegmentKey(self::FLASH_SEGMENTKEY);
if (!array_key_exists($key, $this->data)) {
return new FlashMessageCollection();
}
$messages = $this->data[$key];
$result = new FlashMessageCollection($messages);
unset($this->data[$key]);
$this->data[$key] = [];
return $result;
}
/**
* @return CsrfTokenManager
*/
private function createCsrfTokenManager()
{
$key = $this->getSegmentKey(self::CSRF_SEGMENTKEY);
if (!array_key_exists($key, $this->data)) {
return new CsrfTokenManager();
}
$tokens = $this->data[$key];
$result = new CsrfTokenManager($tokens);
unset($this->data[$key]);
$this->data[$key] = [];
return $result;
}
/**
* @param $name
*
* @throws InvalidArgumentException
*
* @return void
*/
private function ensureSegmentNameFormat($name)
{
if (!preg_match('/^[a-z][a-z0-9_]*$/', $name)) {
throw new InvalidArgumentException('Invalid segment name format.');
}
}
}