first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,82 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session\EventListener;
use Joomla\CMS\Session\MetadataManager;
use Joomla\Registry\Registry;
use Joomla\Session\SessionEvent;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Event listener for session events regarding the session metadata for users.
*
* @since 4.0.0
*/
final class MetadataManagerListener
{
/**
* Session metadata manager.
*
* @var MetadataManager
* @since 4.0.0
*/
private $metadataManager;
/**
* Application configuration.
*
* @var Registry
* @since 4.0.0
*/
private $config;
/**
* Constructor.
*
* @param MetadataManager $metadataManager Session metadata manager.
* @param Registry $config Application configuration.
*
* @since 4.0.0
*/
public function __construct(MetadataManager $metadataManager, Registry $config)
{
$this->metadataManager = $metadataManager;
$this->config = $config;
}
/**
* Listener for the `session.start` event.
*
* @param SessionEvent $event The session event.
*
* @return void
*
* @since 4.0.0
*/
public function onAfterSessionStart(SessionEvent $event)
{
// Whether to track Session Metadata
if (!$this->config->get('session_metadata', true) || !$event->getSession()->has('user')) {
return;
}
$user = $event->getSession()->get('user');
// Whether to track Session Metadata for Guest user
if (!$this->config->get('session_metadata_for_guest', true) && !$user->id) {
return;
}
$this->metadataManager->createOrUpdateRecord($event->getSession(), $user);
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session\Exception;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Exception class defining an unsupported session storage object
*
* @since 3.6.3
*/
class UnsupportedStorageException extends \RuntimeException
{
}

View File

@ -0,0 +1,321 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session;
use Joomla\Application\AbstractApplication;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\User\User;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Database\ParameterType;
use Joomla\Session\SessionInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Manager for optional session metadata.
*
* @since 3.8.6
* @internal
*/
final class MetadataManager
{
/**
* Internal variable indicating a session record exists.
*
* @var integer
* @since 4.0.0
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
*/
private static $sessionRecordExists = 1;
/**
* Internal variable indicating a session record does not exist.
*
* @var integer
* @since 4.0.0
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
*/
private static $sessionRecordDoesNotExist = 0;
/**
* Internal variable indicating an unknown session record statue.
*
* @var integer
* @since 4.0.0
* @note Once PHP 7.1 is the minimum supported version this should become a private constant
*/
private static $sessionRecordUnknown = -1;
/**
* Application object.
*
* @var AbstractApplication
* @since 3.8.6
*/
private $app;
/**
* Database driver.
*
* @var DatabaseInterface
* @since 3.8.6
*/
private $db;
/**
* MetadataManager constructor.
*
* @param AbstractApplication $app Application object.
* @param DatabaseInterface $db Database driver.
*
* @since 3.8.6
*/
public function __construct(AbstractApplication $app, DatabaseInterface $db)
{
$this->app = $app;
$this->db = $db;
}
/**
* Create the metadata record if it does not exist.
*
* @param SessionInterface $session The session to create the metadata record for.
* @param User $user The user to associate with the record.
*
* @return void
*
* @since 3.8.6
* @throws \RuntimeException
*/
public function createRecordIfNonExisting(SessionInterface $session, User $user)
{
$exists = $this->checkSessionRecordExists($session->getId());
// Only touch the database if the record does not already exist
if ($exists !== self::$sessionRecordExists) {
return;
}
$this->createSessionRecord($session, $user);
}
/**
* Create the metadata record if it does not exist.
*
* @param SessionInterface $session The session to create or update the metadata record for.
* @param User $user The user to associate with the record.
*
* @return void
*
* @since 4.0.0
* @throws \RuntimeException
*/
public function createOrUpdateRecord(SessionInterface $session, User $user)
{
$exists = $this->checkSessionRecordExists($session->getId());
// Do not try to touch the database if we can't determine the record state
if ($exists === self::$sessionRecordUnknown) {
return;
}
if ($exists === self::$sessionRecordDoesNotExist) {
$this->createSessionRecord($session, $user);
return;
}
$this->updateSessionRecord($session, $user);
}
/**
* Delete records with a timestamp prior to the given time.
*
* @param integer $time The time records should be deleted if expired before.
*
* @return void
*
* @since 3.8.6
*/
public function deletePriorTo($time)
{
$query = $this->db->getQuery(true)
->delete($this->db->quoteName('#__session'))
->where($this->db->quoteName('time') . ' < :time')
->bind(':time', $time, ParameterType::INTEGER);
$this->db->setQuery($query);
try {
$this->db->execute();
} catch (ExecutionFailureException $exception) {
// Since garbage collection does not result in a fatal error when run in the session API, we don't allow it here either.
}
}
/**
* Check if the session record exists
*
* @param string $sessionId The session ID to check
*
* @return integer Status value for record presence
*
* @since 4.0.0
*/
private function checkSessionRecordExists(string $sessionId): int
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('session_id'))
->from($this->db->quoteName('#__session'))
->where($this->db->quoteName('session_id') . ' = :session_id')
->bind(':session_id', $sessionId)
->setLimit(1);
$this->db->setQuery($query);
try {
$exists = $this->db->loadResult();
} catch (ExecutionFailureException $e) {
return self::$sessionRecordUnknown;
}
if ($exists) {
return self::$sessionRecordExists;
}
return self::$sessionRecordDoesNotExist;
}
/**
* Create the session record
*
* @param SessionInterface $session The session to create the metadata record for.
* @param User $user The user to associate with the record.
*
* @return void
*
* @since 4.0.0
*/
private function createSessionRecord(SessionInterface $session, User $user)
{
$query = $this->db->getQuery(true);
$time = $session->isNew() ? time() : $session->get('session.timer.start');
$columns = [
$this->db->quoteName('session_id'),
$this->db->quoteName('guest'),
$this->db->quoteName('time'),
$this->db->quoteName('userid'),
$this->db->quoteName('username'),
];
// Add query placeholders
$values = [
':session_id',
':guest',
':time',
':user_id',
':username',
];
// Bind query values
$sessionId = $session->getId();
$userIsGuest = $user->guest;
$userId = $user->id;
$username = $user->username === null ? '' : $user->username;
$query->bind(':session_id', $sessionId)
->bind(':guest', $userIsGuest, ParameterType::INTEGER)
->bind(':time', $time)
->bind(':user_id', $userId, ParameterType::INTEGER)
->bind(':username', $username);
if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false)) {
$clientId = $this->app->getClientId();
$columns[] = $this->db->quoteName('client_id');
$values[] = ':client_id';
$query->bind(':client_id', $clientId, ParameterType::INTEGER);
}
$query->insert($this->db->quoteName('#__session'))
->columns($columns)
->values(implode(', ', $values));
$this->db->setQuery($query);
try {
$this->db->execute();
} catch (ExecutionFailureException $e) {
// This failure isn't critical, we can go on without the metadata
}
}
/**
* Update the session record
*
* @param SessionInterface $session The session to update the metadata record for.
* @param User $user The user to associate with the record.
*
* @return void
*
* @since 4.0.0
*/
private function updateSessionRecord(SessionInterface $session, User $user)
{
$query = $this->db->getQuery(true);
$time = time();
$setValues = [
$this->db->quoteName('guest') . ' = :guest',
$this->db->quoteName('time') . ' = :time',
$this->db->quoteName('userid') . ' = :user_id',
$this->db->quoteName('username') . ' = :username',
];
// Bind query values
$sessionId = $session->getId();
$userIsGuest = $user->guest;
$userId = $user->id;
$username = $user->username === null ? '' : $user->username;
$query->bind(':session_id', $sessionId)
->bind(':guest', $userIsGuest, ParameterType::INTEGER)
->bind(':time', $time)
->bind(':user_id', $userId, ParameterType::INTEGER)
->bind(':username', $username);
if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', false)) {
$clientId = $this->app->getClientId();
$setValues[] = $this->db->quoteName('client_id') . ' = :client_id';
$query->bind(':client_id', $clientId, ParameterType::INTEGER);
}
$query->update($this->db->quoteName('#__session'))
->set($setValues)
->where($this->db->quoteName('session_id') . ' = :session_id');
$this->db->setQuery($query);
try {
$this->db->execute();
} catch (ExecutionFailureException $e) {
// This failure isn't critical, we can go on without the metadata
}
}
}

View File

@ -0,0 +1,340 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\Event\DispatcherInterface;
use Joomla\Session\Session as BaseSession;
use Joomla\Session\StorageInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Class for managing HTTP sessions
*
* @since 1.5
*/
class Session extends BaseSession
{
/**
* Constructor
*
* @param StorageInterface $store A StorageInterface implementation.
* @param DispatcherInterface $dispatcher DispatcherInterface for the session to use.
* @param array $options Optional parameters. Supported keys include:
* - name: The session name
* - id: The session ID
* - expire: The session lifetime in seconds
*
* @since 1.0
*/
public function __construct(StorageInterface $store = null, DispatcherInterface $dispatcher = null, array $options = [])
{
// Extra hash the name of the session for b/c with Joomla 3.x or the session is never found.
if (isset($options['name'])) {
$options['name'] = md5($options['name']);
}
parent::__construct($store, $dispatcher, $options);
}
/**
* Checks for a form token in the request.
*
* Use in conjunction with HTMLHelper::_('form.token') or JSession::getFormToken.
*
* @param string $method The request method in which to look for the token key.
*
* @return boolean True if found and valid, false otherwise.
*
* @since 2.5.4
*/
public static function checkToken($method = 'post')
{
$app = Factory::getApplication();
$token = static::getFormToken();
// Check from header first
if ($token === $app->getInput()->server->get('HTTP_X_CSRF_TOKEN', '', 'alnum')) {
return true;
}
// Then fallback to HTTP query
if (!$app->getInput()->$method->get($token, '', 'alnum')) {
if ($app->getSession()->isNew()) {
// Redirect to login screen.
$app->enqueueMessage(Text::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'), 'warning');
$app->redirect(Route::_('index.php'));
return true;
}
return false;
}
return true;
}
/**
* Method to determine a hash for anti-spoofing variable names
*
* @param boolean $forceNew If true, force a new token to be created
*
* @return string Hashed var name
*
* @since 1.6
*/
public static function getFormToken($forceNew = false)
{
$user = Factory::getUser();
return ApplicationHelper::getHash($user->get('id', 0) . Factory::getApplication()->getSession()->getToken($forceNew));
}
/**
* Get the available session handlers
*
* @return array An array of available session handlers
*
* @since 4.0.0
*/
public static function getHandlers(): array
{
$connectors = [];
// Get an iterator and loop through the handler classes.
$iterator = new \DirectoryIterator(JPATH_LIBRARIES . '/vendor/joomla/session/src/Handler');
foreach ($iterator as $file) {
$fileName = $file->getFilename();
// Only load for PHP files.
if (!$file->isFile() || $file->getExtension() !== 'php') {
continue;
}
// Derive the class name from the type.
$class = str_ireplace('.php', '', '\\Joomla\\Session\\Handler\\' . $fileName);
// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
if (!class_exists($class)) {
continue;
}
// Sweet! Our class exists, so now we just need to know if it passes its test method.
if ($class::isSupported()) {
// Connector names should not have file the handler suffix or the file extension.
$connectors[] = str_ireplace('Handler.php', '', $fileName);
}
}
return $connectors;
}
/**
* Returns the global session object.
*
* @return static The Session object.
*
* @since 1.5
*
* @deprecated 4.3 will be removed in 6.0
* Load the session service from the dependency injection container or via $app->getSession()
* Example: Factory::getApplication()->getSession();
*/
public static function getInstance()
{
@trigger_error(
__METHOD__ . '() is deprecated. Load the session from the dependency injection container or via Factory::getApplication()->getSession().',
E_USER_DEPRECATED
);
return Factory::getApplication()->getSession();
}
/**
* Get data from the session store
*
* @param string $name Name of a variable
* @param mixed $default Default value of a variable if not set
*
* @return mixed Value of a variable
*
* @since 1.5
*/
public function get($name, $default = null)
{
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
if (\func_num_args() > 2) {
$args = \func_get_args();
if (!empty($args[2])) {
@trigger_error(
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
. 'The namespace should be prepended to the name instead.',
E_USER_DEPRECATED
);
$name = $args[2] . '.' . $name;
}
}
if (parent::has($name)) {
// Parent is used because of b/c, can be changed in Joomla 6
return parent::get($name, $default);
}
/*
* B/C for retrieving sessions that originated in Joomla 3.
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
* This is no longer the case in Joomla 4 and will be converted
* when saving new values in `self::set()`
*/
if (strpos($name, '.') !== false && parent::has('__' . $name)) {
return parent::get('__' . $name, $default);
}
// More b/c for retrieving sessions that originated in Joomla 3. This will be removed in Joomla 6
// as no sessions should have this format anymore!
if (parent::has('__default.' . $name)) {
return parent::get('__default.' . $name, $default);
}
return $default;
}
/**
* Set data into the session store.
*
* @param string $name Name of a variable.
* @param mixed $value Value of a variable.
*
* @return mixed Old value of a variable.
*
* @since 1.5
*/
public function set($name, $value = null)
{
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
if (\func_num_args() > 2) {
$args = \func_get_args();
if (!empty($args[2])) {
@trigger_error(
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
. 'The namespace should be prepended to the name instead.',
E_USER_DEPRECATED
);
$name = $args[2] . '.' . $name;
}
}
return parent::set($name, $value);
}
/**
* Check whether data exists in the session store
*
* @param string $name Name of variable
*
* @return boolean True if the variable exists
*
* @since 1.5
*/
public function has($name)
{
// Handle B/C by checking if a namespace was passed to the method, will be removed at 5.0
if (\func_num_args() > 1) {
$args = \func_get_args();
if (!empty($args[1])) {
@trigger_error(
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
. 'The namespace should be prepended to the name instead.',
E_USER_DEPRECATED
);
$name = $args[1] . '.' . $name;
}
}
if (parent::has($name)) {
return true;
}
/*
* B/C for retrieving sessions that originated in Joomla 3.
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
* This is no longer the case in Joomla 4 and will be converted
* when saving new values in `self::set()`
*/
if (strpos($name, '.') !== false && parent::has('__' . $name)) {
return true;
}
// More b/c for retrieving sessions that originated in Joomla 3. This will be removed in Joomla 6
// as no sessions should have this format anymore!
return parent::has('__default.' . $name);
}
/**
* Clears all variables from the session store
*
* @return void
*
* @since 1.5
*/
public function clear()
{
// Handle B/C by checking if parameters were passed to this method; if so proxy to the new remove() method, will be removed at 5.0
if (\func_num_args() >= 1) {
$args = \func_get_args();
if (!empty($args[0])) {
@trigger_error(
'Using ' . __METHOD__ . '() to remove a single element from the session is deprecated. Use ' . __CLASS__ . '::remove() instead.',
E_USER_DEPRECATED
);
$name = $args[0];
// Also check for a namespace
if (\func_num_args() > 1 && !empty($args[1])) {
@trigger_error(
'Passing a namespace as a parameter to ' . __METHOD__ . '() is deprecated. '
. 'The namespace should be prepended to the name instead.',
E_USER_DEPRECATED
);
$name = $args[1] . '.' . $name;
}
$this->remove($name);
/*
* B/C for cleaning sessions that originated in Joomla 3.
* A namespace before Joomla 4 has a prefix of 2 underscores (__).
* This is no longer the case in Joomla 4 so we clean both variants.
*/
$this->remove('__' . $name);
return;
}
}
parent::clear();
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\ContainerAwareInterface;
use Joomla\DI\ContainerAwareTrait;
use Joomla\Registry\Registry;
use Joomla\Session\Handler;
use Joomla\Session\HandlerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Factory for creating session API objects
*
* @since 4.0.0
*/
class SessionFactory implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* Create a session handler based on the application configuration.
*
* @param array $options The options used to instantiate the SessionInterface instance.
*
* @return HandlerInterface
*
* @since 4.0.0
*/
public function createSessionHandler(array $options): HandlerInterface
{
$resolver = new OptionsResolver();
$this->configureSessionHandlerOptions($resolver);
$options = $resolver->resolve($options);
/** @var Registry $config */
$config = $this->getContainer()->get('config');
$handlerType = $config->get('session_handler', 'filesystem');
switch ($handlerType) {
case 'apcu':
if (!Handler\ApcuHandler::isSupported()) {
throw new \RuntimeException('APCu is not supported on this system.');
}
return new Handler\ApcuHandler();
case 'database':
return new Handler\DatabaseHandler($this->getContainer()->get(DatabaseInterface::class));
case 'filesystem':
case 'none':
// Try to use a custom configured path, fall back to the path in the PHP runtime configuration
$path = $config->get('session_filesystem_path', ini_get('session.save_path'));
// If we still have no path, as a last resort fall back to the system's temporary directory
if (empty($path)) {
$path = sys_get_temp_dir();
}
return new Handler\FilesystemHandler($path);
case 'memcached':
if (!Handler\MemcachedHandler::isSupported()) {
throw new \RuntimeException('Memcached is not supported on this system.');
}
$host = $config->get('session_memcached_server_host', 'localhost');
$port = $config->get('session_memcached_server_port', 11211);
$memcached = new \Memcached($config->get('session_memcached_server_id', 'joomla_cms'));
$memcached->addServer($host, $port);
ini_set('session.save_path', "$host:$port");
ini_set('session.save_handler', 'memcached');
return new Handler\MemcachedHandler($memcached, ['ttl' => $options['expire']]);
case 'redis':
if (!Handler\RedisHandler::isSupported()) {
throw new \RuntimeException('Redis is not supported on this system.');
}
$redis = new \Redis();
$host = $config->get('session_redis_server_host', '127.0.0.1');
// Use default port if connecting over a socket whatever the config value
$port = $host[0] === '/' ? 0 : $config->get('session_redis_server_port', 6379);
if ($config->get('session_redis_persist', true)) {
$redis->pconnect(
$host,
$port
);
} else {
$redis->connect(
$host,
$port
);
}
if (!empty($config->get('session_redis_server_auth', ''))) {
$redis->auth($config->get('session_redis_server_auth', null));
}
$db = (int) $config->get('session_redis_server_db', 0);
if ($db !== 0) {
$redis->select($db);
}
return new Handler\RedisHandler($redis, ['ttl' => $options['expire']]);
default:
throw new \InvalidArgumentException(sprintf('The "%s" session handler is not recognised.', $handlerType));
}
}
/**
* Resolve the options for the session handler.
*
* @param OptionsResolver $resolver The options resolver.
*
* @return void
*
* @since 4.0.0
*/
protected function configureSessionHandlerOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'force_ssl' => false,
]
);
$resolver->setRequired(['name', 'expire']);
$resolver->setAllowedTypes('name', ['string']);
$resolver->setAllowedTypes('expire', ['int']);
$resolver->setAllowedTypes('force_ssl', ['bool']);
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Manager for interacting with the session handler to perform updates on sessions.
*
* @since 4.0.0
*/
final class SessionManager
{
/**
* Session handler.
*
* @var \SessionHandlerInterface
* @since 4.0.0
*/
private $sessionHandler;
/**
* SessionManager constructor.
*
* @param \SessionHandlerInterface $sessionHandler Session handler.
*
* @since 4.0.0
*/
public function __construct(\SessionHandlerInterface $sessionHandler)
{
$this->sessionHandler = $sessionHandler;
}
/**
* Destroys the given session ID.
*
* @param string $sessionId The session ID to destroy.
*
* @return boolean
*
* @since 4.0.0
*/
public function destroySession(string $sessionId): bool
{
return $this->sessionHandler->destroy($sessionId);
}
/**
* Destroys the given session IDs.
*
* @param string[] $sessionIds The session IDs to destroy.
*
* @return boolean
*
* @since 4.0.0
*/
public function destroySessions(array $sessionIds): bool
{
$result = true;
foreach ($sessionIds as $sessionId) {
if (!$this->destroySession($sessionId)) {
$result = false;
}
}
return $result;
}
}

View File

@ -0,0 +1,320 @@
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Session\Storage;
use Joomla\Input\Input;
use Joomla\Registry\Registry;
use Joomla\Session\Storage\NativeStorage;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Service provider for the application's session dependency
*
* @since 4.0.0
*/
class JoomlaStorage extends NativeStorage
{
/**
* Internal data store for the session data
*
* @var Registry
* @since 4.0.0
*/
private $data;
/**
* Force cookies to be SSL only
*
* @var boolean
* @since 4.0.0
*/
private $forceSSL = false;
/**
* The domain to set in the session cookie
*
* @var string
* @since 5.0.0
*/
private $cookieDomain = '';
/**
* The path to set in the session cookie
*
* @var string
* @since 5.0.0
*/
private $cookiePath = '/';
/**
* Input object
*
* @var Input
* @since 4.0.0
*/
private $input;
/**
* Constructor
*
* @param Input $input Input object
* @param \SessionHandlerInterface $handler Session save handler
* @param array $options Session options
*
* @since 4.0.0
*/
public function __construct(Input $input, \SessionHandlerInterface $handler = null, array $options = [])
{
// Disable transparent sid support and default use cookies
$options += [
'use_cookies' => 1,
'use_trans_sid' => 0,
];
if (!headers_sent() && !$this->isActive()) {
session_cache_limiter('none');
}
$this->setOptions($options);
$this->setHandler($handler);
$this->setCookieParams();
$this->data = new Registry();
$this->input = $input;
// Register our function as shutdown method, so we can manipulate it
register_shutdown_function([$this, 'close']);
}
/**
* Retrieves all variables from the session store
*
* @return array
*
* @since 4.0.0
*/
public function all(): array
{
return $this->data->toArray();
}
/**
* Clears all variables from the session store
*
* @return void
*
* @since 4.0.0
*/
public function clear(): void
{
$session_name = $this->getName();
/*
* In order to kill the session altogether, such as to log the user out, the session id
* must also be unset. If a cookie is used to propagate the session id (default behavior),
* then the session cookie must be deleted.
*/
if (isset($_COOKIE[$session_name])) {
$cookie = session_get_cookie_params();
setcookie($session_name, '', time() - 42000, $this->cookiePath, $this->cookieDomain, $cookie['secure'], true);
}
$this->data = new Registry();
}
/**
* Writes session data and ends session
*
* @return void
*
* @see session_write_close()
* @since 4.0.0
*/
public function close(): void
{
// Before storing data to the session, we serialize and encode the Registry
$_SESSION['joomla'] = base64_encode(serialize($this->data));
parent::close();
}
/**
* Get data from the session store
*
* @param string $name Name of a variable
* @param mixed $default Default value of a variable if not set
*
* @return mixed Value of a variable
*
* @since 4.0.0
*/
public function get(string $name, $default)
{
if (!$this->isStarted()) {
$this->start();
}
return $this->data->get($name, $default);
}
/**
* Check whether data exists in the session store
*
* @param string $name Name of variable
*
* @return boolean True if the variable exists
*
* @since 4.0.0
*/
public function has(string $name): bool
{
if (!$this->isStarted()) {
$this->start();
}
return $this->data->exists($name);
}
/**
* Unset a variable from the session store
*
* @param string $name Name of variable
*
* @return mixed The value from session or NULL if not set
*
* @since 4.0.0
*/
public function remove(string $name)
{
if (!$this->isStarted()) {
$this->start();
}
$old = $this->data->get($name);
unset($this->data[$name]);
return $old;
}
/**
* Set data into the session store
*
* @param string $name Name of a variable.
* @param mixed $value Value of a variable.
*
* @return mixed Old value of a variable.
*
* @since 4.0.0
*/
public function set(string $name, $value = null)
{
if (!$this->isStarted()) {
$this->start();
}
$old = $this->data->get($name);
$this->data->set($name, $value);
return $old;
}
/**
* Set session cookie parameters
*
* @return void
*
* @since 4.0.0
*/
protected function setCookieParams(): void
{
if (headers_sent() || $this->isActive()) {
return;
}
$cookie = session_get_cookie_params();
if ($this->forceSSL) {
$cookie['secure'] = true;
}
if ($this->cookieDomain !== '') {
$cookie['domain'] = $this->cookieDomain;
}
if ($this->cookiePath !== '') {
$cookie['path'] = $this->cookiePath;
}
session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true);
}
/**
* Sets session options
*
* @param array $options Session ini directives array(key => value).
*
* @return $this
*
* @link http://php.net/session.configuration
* @since 4.0.0
*/
public function setOptions(array $options): NativeStorage
{
if (isset($options['cookie_domain'])) {
$this->cookieDomain = $options['cookie_domain'];
}
if (isset($options['cookie_path'])) {
$this->cookiePath = $options['cookie_path'];
}
if (isset($options['force_ssl'])) {
$this->forceSSL = (bool) $options['force_ssl'];
}
return parent::setOptions($options);
}
/**
* Start a session
*
* @return void
*
* @since 4.0.0
*/
public function start(): void
{
$session_name = $this->getName();
// Get the cookie object
$cookie = $this->input->cookie;
if (\is_null($cookie->get($session_name))) {
$session_clean = $this->input->getString($session_name);
if ($session_clean) {
$this->setId($session_clean);
$cookie->set($session_name, '', time() - 3600);
}
}
parent::start();
// Try loading data from the session
if (!empty($_SESSION['joomla'])) {
$this->data = unserialize(base64_decode($_SESSION['joomla']));
}
}
}