* @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\CMS\Session\Storage; use Joomla\CMS\Factory; use Joomla\Input\Input; use Joomla\Registry\Registry; use Joomla\Session\Storage\NativeStorage; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') 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; /** * 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])) { $app = Factory::getApplication(); $cookie_domain = $app->get('cookie_domain', ''); $cookie_path = $app->get('cookie_path', '/'); $cookie = session_get_cookie_params(); setcookie($session_name, '', time() - 42000, $cookie_path, $cookie_domain, $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(clone $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; } $app = Factory::getApplication(); if ($app->get('cookie_domain', '') != '') { $cookie['domain'] = $app->get('cookie_domain'); } if ($app->get('cookie_path', '') != '') { $cookie['path'] = $app->get('cookie_path'); } 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 * * @see http://php.net/session.configuration * @since 4.0.0 */ public function setOptions(array $options): NativeStorage { 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 (isset($_SESSION['joomla']) && !empty($_SESSION['joomla'])) { $this->data = unserialize(base64_decode($_SESSION['joomla'])); } } }