* @license GNU General Public License version 2 or later; see LICENSE */ namespace Joomla\CMS\Session; use InvalidArgumentException; 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 Memcached; use Redis; use RuntimeException; use Symfony\Component\OptionsResolver\OptionsResolver; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') 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']]); case 'wincache': // @TODO Remove WinCache with Joomla 5.0 if (!Handler\WincacheHandler::isSupported()) { throw new RuntimeException('Wincache is not supported on this system.'); } return new Handler\WincacheHandler(); 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']); } }