Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,114 @@
<?php
namespace Xentral\Components\Http\Session;
use Xentral\Components\Http\Exception\CsrfTokenException;
use Xentral\Components\Http\Exception\InvalidArgumentException;
use Xentral\Components\Util\StringUtil;
final class CsrfTokenManager
{
/** @var int CSRF_TOKEN_LENGTH */
const CSRF_TOKEN_LENGTH = 32;
/** @var array $tokenData */
private $tokenData;
/**
* @param array $tokenData
*/
public function __construct($tokenData = [])
{
$this->tokenData = $tokenData;
}
/**
* @param string $key
* @param string $value
* @param bool $remove
*
* @return bool
*/
public function isTokenValid($key, $value, $remove = true)
{
$this->ensureTokenKeyFormat($key);
$valid = false;
if (array_key_exists($key, $this->tokenData) && $this->tokenData[$key] === $value) {
$valid = true;
}
if ($valid === true && $remove === true) {
$this->removeToken($key);
}
return $valid;
}
/**
* @param $key
*
* @throws CsrfTokenException
*
* @return string
*/
public function createToken($key)
{
$this->ensureTokenKeyFormat($key);
$token = StringUtil::random(self::CSRF_TOKEN_LENGTH, true);
$this->tokenData[$key] = $token;
if (strlen($token) < self::CSRF_TOKEN_LENGTH) {
throw new CsrfTokenException('Could not create CSRF token.');
}
return $token;
}
/**
* @param $key
*
* @return void
*/
public function removeToken($key)
{
$this->ensureTokenKeyFormat($key);
unset($this->tokenData[$key]);
}
/**
* @param string $key
*
* @throws InvalidArgumentException
*
* @return string
*/
public function refreshToken($key)
{
$this->ensureTokenKeyFormat($key);
$this->removeToken($key);
return $this->createToken($key);
}
/**
* @param $target
*
* @return void
*/
public function dumpTokens(&$target)
{
$target = $this->tokenData;
}
/**
* @param string $key
*
* @throws InvalidArgumentException
*
* @return void
*/
private function ensureTokenKeyFormat($key)
{
if (!preg_match('/^[a-z][a-z0-9_]*$/', $key)) {
throw new InvalidArgumentException('Invalid token key format.');
}
}
}
@@ -0,0 +1,145 @@
<?php
namespace Xentral\Components\Http\Session;
final class FlashMessageCollection
{
/** @var FlashMessageData[] $data */
public $data;
/**
* FlashMessageCollection constructor.
*
* @param array $flashMessageData array structure from session
*/
public function __construct($flashMessageData = [])
{
$data = $this->fromSessionArray($flashMessageData);
$this->data = $this->sortByPriority($data);
}
/**
* Gets and removes all flash messages from previous session
*
* @return FlashMessageData[]
*/
public function getAll()
{
$result = $this->data;
$this->data = [];
return $result;
}
/**
* Gets and removes flash messages filtered
*
* @param string|null $segment filter for segment (null=deactivated)
* @param string|null $type filter for type (null=deactivated)
*
* @return FlashMessageData[] sorted by priority
*/
public function getMessages($segment = null, $type = null)
{
$result = [];
foreach ($this->data as $key => $item) {
if (
($segment === null || $segment === $item->getSegmentName())
&& ($type === null || $type === $item->getType())
) {
$result[] = $item;
unset($this->data[$key]);
}
}
return $result;
}
/**
* Gets and removes flash messages filtered
*
* @param string $type
*
* @return FlashMessageData[] sorted by priority
*/
public function getMessagesByType($type)
{
return $this->getMessages(null, $type);
}
/**
* Gets and removes flash messages filtered
*
* @param string $segment
*
* @return FlashMessageData[] sorted by priority
*/
public function getMessagesBySegment($segment)
{
return $this->getMessages($segment, null);
}
/**
* Returns array to be stored in session
*
* @return array
*/
public function toSessionArray()
{
$result = [];
foreach ($this->data as $item) {
$result[] = $item->toSessionArray();
}
return $result;
}
/**
* Sorts flash messages by priority (descending)
*
* @param FlashMessageData[] $messages
*
* @return FlashMessageData[]
*/
public function sortByPriority($messages)
{
usort($messages, [$this, 'comparePriorityCallback']);
return $messages;
}
/**
* @param array $data
*
* @return FlashMessageData[]
*/
private function fromSessionArray($data)
{
$result = [];
foreach ($data as $item) {
$result[] = FlashMessageData::createFromArray($item);
}
return $result;
}
/**
* Callable compare function for sorting.
*
* @param FlashMessageData $insert
* @param FlashMessageData $exist
*
* @return int
*/
private function comparePriorityCallback($insert, $exist)
{
if ($insert->getPriority() === $exist->getPriority()) {
return 0;
}
if ($insert->getPriority() > $exist->getPriority()) {
return -1;
}
return 1;
}
}
@@ -0,0 +1,155 @@
<?php
namespace Xentral\Components\Http\Session;
use Xentral\Components\Http\Exception\InvalidArgumentException;
final class FlashMessageData
{
/** @var string FLASHTYPE_DEFAULT */
const FLASHTYPE_DEFAULT = 'default';
/** @var string FLASHTYPE_NOTICE */
const FLASHTYPE_NOTICE = 'notice';
/** @var string FLASHTYPE_SUCCESS */
const FLASHTYPE_SUCCESS = 'success';
/** @var string FLASHTYPE_WARNING */
const FLASHTYPE_WARNING = 'warning';
/** @var string FLASHTYPE_ERROR */
const FLASHTYPE_ERROR = 'error';
/** @var array $flashTypes */
public static $flashTypes = [
self::FLASHTYPE_DEFAULT,
self::FLASHTYPE_NOTICE,
self::FLASHTYPE_SUCCESS,
self::FLASHTYPE_WARNING,
self::FLASHTYPE_ERROR,
];
/**@var string $type */
private $type;
/** @var string $message */
private $message;
/** @var int $priority */
private $priority;
/** @var string $segmentName */
private $segmentName;
/**
* @param string $message
* @param string $type
* @param string $segmentName
* @param int $priority
*/
public function __construct($message, $type, $segmentName = '', $priority = 0)
{
$this->setType($type);
$this->message = $message;
$this->setSegmentName($segmentName);
$this->priority = (int)$priority;
}
/**
* Create FlashMessageData object from session array entry
*
* @param array $data required keys: 'priority', 'segment, 'type', 'message'
*
* @throws InvalidArgumentException
*
* @return FlashMessageData
*/
public static function createFromArray($data)
{
if (
!array_key_exists('priority', $data)
|| !array_key_exists('segment', $data)
|| !array_key_exists('type', $data)
|| !array_key_exists('message', $data)
) {
throw new InvalidArgumentException('Invalid array data for FlashMessageData.');
}
return new self($data['message'], $data['type'], $data['segment'], $data['priority']);
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @return int
*/
public function getPriority()
{
return $this->priority;
}
/**
* @return bool
*/
public function getSegmentName()
{
return $this->segmentName;
}
/**
* Returns array to store in the session
*
* @return array
*/
public function toSessionArray()
{
return [
'priority' => $this->getPriority(),
'segment' => $this->getSegmentName(),
'type' => $this->getType(),
'message' => $this->getMessage(),
];
}
/**
* @param string $type
*
* @throws InvalidArgumentException
*/
private function setType($type)
{
if (!in_array($type, self::$flashTypes, true)) {
throw new InvalidArgumentException(sprintf('Unknown message type "%s".', $type));
}
$this->type = $type;
}
/**
* @param string $name
*
* @return void
*/
private function setSegmentName($name)
{
if (!preg_match('/^[a-z0-9_]*$/', $name)) {
throw new InvalidArgumentException(sprintf('Invalid Segment Name "%s".', $name));
}
$this->segmentName = $name;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace Xentral\Components\Http\Session;
use Xentral\Components\Http\Exception\InvalidArgumentException;
final class Segment
{
/** @var Session $session */
private $session;
/** @var string $name */
private $name;
/** @var array $data */
private $data;
/**
* @param Session $session
* @param string $name
* @param array $data
*/
public function __construct(Session $session, $name, $data = [])
{
$this->session = $session;
$this->name = $name;
$this->data = $data;
}
/**
* Makes an entry to the segment
*
* @param string $key
* @param int|float|string|array $value
*
* @return void
*/
public function setValue($key, $value)
{
$this->ensureSegmentKeyFormat($key);
$this->data[$key] = $value;
}
/**
* Gets a value with a specific key
*
* @param string $key
* @param mixed|null $default
* @param bool $clear true=remove entry from the session
*
* @return mixed|null
*/
public function getValue($key, $default = null, $clear = false)
{
$this->ensureSegmentKeyFormat($key);
$value = $default;
if (isset($this->data[$key])) {
$value = $this->data[$key];
}
if ($clear === true) {
$this->removeValue($key);
}
return $value;
}
/**
* Adds new flash message to the segment
*
* @internal The actual segment which holds the flashes is 'flash_messages'.
*
* @param string $message
* @param string $type
* @param int $priority sorted highest to lowest
*
* @return void
*/
public function addFlashMessage($message, $type = FlashMessageData::FLASHTYPE_DEFAULT, $priority = 0)
{
$this->session->addFlashMessage($this->name, $message, $type, $priority);
}
/**
* Removes single entry
*
* @param string $key
*
* @return void
*/
public function removeValue($key)
{
$this->ensureSegmentKeyFormat($key);
unset($this->data[$key]);
}
/**
* Removes all entries and flashes of this segment
*
* @return void
*/
public function clearAll()
{
$this->data = [];
$this->session->getFlashMessages($this->name);
}
/**
* Gets all entries
*
* @return array
*/
public function getAll()
{
return $this->data;
}
/**
* @param string $key
*
* @throws InvalidArgumentException
*
* @return void
*/
private function ensureSegmentKeyFormat($key)
{
if (!preg_match('/^[a-z][a-z0-9_]*$/', $key)) {
throw new InvalidArgumentException('Invalid segment key format.');
}
}
}
+315
View File
@@ -0,0 +1,315 @@
<?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.');
}
}
}
@@ -0,0 +1,63 @@
<?php
namespace Xentral\Components\Http\Session;
use Xentral\Components\Http\Exception\SessionException;
class SessionHandler
{
/**
* Create a session object with actual session data
*
* @throws SessionException
*
* @return Session Session object
*/
public static function createSession()
{
if (!extension_loaded('session')) {
throw new SessionException('PHP extension "session" is missing.');
}
if (self::isSessionStarted()) {
throw new SessionException('Failed to create session. Session can be started only once.');
}
if (session_status() === PHP_SESSION_DISABLED) {
throw new SessionException('Failed to create session. Sessions are disabled.');
}
$isStarted = session_start();
if ($isStarted === false) {
throw new SessionException('Failed to create session. Initialization failed.');
}
if (session_status() === PHP_SESSION_NONE) {
throw new SessionException('Failed to create session. Unexpected status: PHP_SESSION_NONE');
}
return new Session($_SESSION);
}
/**
* @return bool
*/
public static function isSessionStarted()
{
$status = session_status();
return $status === PHP_SESSION_ACTIVE;
}
/**
* Save and close the session
*
* @param Session $session
*
* @return void
*/
public static function commitSession(Session $session)
{
$session->dumpSession($_SESSION);
session_write_close();
}
}