A super easy PHP Framework for web development!
				https://github.com/exacti/phacil-framework
			
			
		
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							236 lines
						
					
					
						
							5.5 KiB
						
					
					
				
			
		
		
	
	
							236 lines
						
					
					
						
							5.5 KiB
						
					
					
				<?php
 | 
						|
/**
 | 
						|
 * Copyright © 2024 ExacTI Technology Solutions. All rights reserved.
 | 
						|
 * GPLv3 General License.
 | 
						|
 * https://exacti.com.br
 | 
						|
 * Phacil PHP Framework - https://github.com/exacti/phacil-framework
 | 
						|
 */
 | 
						|
 | 
						|
 namespace Phacil\Framework\Session\Handlers;
 | 
						|
 | 
						|
use Phacil\Framework\Api\Database as FrameworkDatabase;
 | 
						|
use Phacil\Framework\Encryption;
 | 
						|
use Phacil\Framework\Config as FrameworkConfig;
 | 
						|
 | 
						|
/**
 | 
						|
 * Data base session save handler
 | 
						|
 * @since 2.0.0
 | 
						|
 * @package Phacil\Framework\Session;
 | 
						|
 */
 | 
						|
class Database implements \Phacil\Framework\Session\Api\HandlerInterface
 | 
						|
{
 | 
						|
	const SHORT_NAME = 'database';
 | 
						|
 | 
						|
	const TABLE_NAME = 'session';
 | 
						|
 | 
						|
	const COLUMN_DATA = 'session_data';
 | 
						|
 | 
						|
	const COLUMN_ID = 'session_id';
 | 
						|
 | 
						|
	const COLUMN_EXPIRES = 'session_expires';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Session data table name
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	protected $_sessionTable;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Database write connection
 | 
						|
	 * 
 | 
						|
	 * @var \Phacil\Framework\Databases\Api\Object\ResultInterface|\Phacil\Framework\Database::Cache|\Phacil\Framework\MagiQL
 | 
						|
	 */
 | 
						|
	protected $connection;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * 
 | 
						|
	 * @var \Phacil\Framework\Encryption
 | 
						|
	 */
 | 
						|
	private $encryptor;
 | 
						|
 | 
						|
	private $config;
 | 
						|
	
 | 
						|
	/**
 | 
						|
	 * @param \Phacil\Framework\Api\Database $resource 
 | 
						|
	 * @param \Phacil\Framework\Encryption $encryptor 
 | 
						|
	 * @return void 
 | 
						|
	 */
 | 
						|
	public function __construct(
 | 
						|
		FrameworkDatabase $resource,
 | 
						|
		Encryption $encryptor,
 | 
						|
		FrameworkConfig $config
 | 
						|
	) {
 | 
						|
		$this->_sessionTable = self::TABLE_NAME;
 | 
						|
		$this->connection = $resource->query();
 | 
						|
		$this->checkConnection();
 | 
						|
		$this->encryptor = $encryptor;
 | 
						|
		$this->config = $config;
 | 
						|
	}
 | 
						|
 | 
						|
	public function getFailedLockAttempts() { }
 | 
						|
 | 
						|
	public function setName($name) { }
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param string $hash 
 | 
						|
	 * @return string|false 
 | 
						|
	 */
 | 
						|
	private function hashed($hash) {
 | 
						|
		//$this->encryptor->setHashAlgo('sha256');
 | 
						|
		return $this->encryptor->hash($hash) ?: $hash;
 | 
						|
		//return $hash;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check DB connection
 | 
						|
	 *
 | 
						|
	 * @return void
 | 
						|
	 * @throws \Magento\Framework\Exception\SessionException
 | 
						|
	 */
 | 
						|
	protected function checkConnection()
 | 
						|
	{
 | 
						|
		if (!$this->connection) {
 | 
						|
			throw new \Phacil\Framework\Exception(
 | 
						|
				"The write connection to the database isn't available. Please try again later."
 | 
						|
			);
 | 
						|
		}
 | 
						|
		if (!$this->connection->isTableExists($this->_sessionTable)) {
 | 
						|
			throw new \Phacil\Framework\Exception(
 | 
						|
				"The database storage table doesn't exist. Verify the table and try again."
 | 
						|
			);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/** @return int  */
 | 
						|
	protected function getLifetime() {
 | 
						|
		return (int)$this->config->get('session_expire') ?: self::DEFAULT_SESSION_LIFETIME;
 | 
						|
	}
 | 
						|
 | 
						|
	/** @return int  */
 | 
						|
	protected function getFirstLifetime() {
 | 
						|
		return (int)$this->config->get('session_first_lifetime') ?: self::DEFAULT_SESSION_FIRST_LIFETIME;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Open session
 | 
						|
	 *
 | 
						|
	 * @param string $savePath ignored
 | 
						|
	 * @param string $sessionName ignored
 | 
						|
	 * @return bool
 | 
						|
	 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function open($savePath, $sessionName)
 | 
						|
	{
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Close session
 | 
						|
	 *
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function close()
 | 
						|
	{
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Fetch session data
 | 
						|
	 *
 | 
						|
	 * @param string $sessionId
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function read($sessionId)
 | 
						|
	{
 | 
						|
		// need to use write connection to get the most fresh DB sessions
 | 
						|
		$select = $this->connection->select()->from(
 | 
						|
			$this->_sessionTable
 | 
						|
		);
 | 
						|
		$select->where()->eq(self::COLUMN_ID, $this->hashed($sessionId))->end();
 | 
						|
		
 | 
						|
		$data = $select->load();
 | 
						|
 | 
						|
		if($data->getNumRows() < 1){
 | 
						|
			return null;
 | 
						|
		}
 | 
						|
 | 
						|
		if($data->getRow()->getValue(self::COLUMN_EXPIRES) < time()){
 | 
						|
			$this->destroy($sessionId);
 | 
						|
			return null;
 | 
						|
		}
 | 
						|
 | 
						|
		$data = $data->getRow()->getValue(self::COLUMN_DATA);
 | 
						|
 | 
						|
		// check if session data is a base64 encoded string
 | 
						|
		$decodedData = base64_decode($data, true);
 | 
						|
		if ($decodedData !== false) {
 | 
						|
			$data = $decodedData;
 | 
						|
		}
 | 
						|
		return $data;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Update session
 | 
						|
	 *
 | 
						|
	 * @param string $sessionId
 | 
						|
	 * @param string $sessionData
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function write($sessionId, $sessionData)
 | 
						|
	{
 | 
						|
		$hashedSessionId = $this->hashed($sessionId);
 | 
						|
		$select = $this->connection->select()->from($this->_sessionTable);
 | 
						|
		$select->where()->eq(self::COLUMN_ID, $hashedSessionId)->end();
 | 
						|
		$exists = $select->load();
 | 
						|
 | 
						|
		// encode session serialized data to prevent insertion of incorrect symbols
 | 
						|
		$sessionData = base64_encode($sessionData);
 | 
						|
		$bind = [self::COLUMN_EXPIRES => time() + $this->getLifetime(), self::COLUMN_DATA => $sessionData];
 | 
						|
 | 
						|
		if ($exists->getNumRows() > 0) {
 | 
						|
			$update = $this->connection->update($this->_sessionTable, $bind);
 | 
						|
			$update->where()->eq(self::COLUMN_ID, $hashedSessionId)->end();
 | 
						|
			$update->load();
 | 
						|
		} else {
 | 
						|
			$bind[self::COLUMN_ID] = $hashedSessionId;
 | 
						|
			$bind[self::COLUMN_EXPIRES] = time() + $this->getFirstLifetime();
 | 
						|
			$this->connection->insert($this->_sessionTable, $bind)->load();
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Destroy session
 | 
						|
	 *
 | 
						|
	 * @param string $sessionId
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function destroy($sessionId)
 | 
						|
	{
 | 
						|
		$del = $this->connection->delete($this->_sessionTable);
 | 
						|
		$del->where()->eq(self::COLUMN_ID, $this->hashed($sessionId))->end();
 | 
						|
		return $del->load();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Garbage collection
 | 
						|
	 *
 | 
						|
	 * @param int $maxLifeTime
 | 
						|
	 * @return bool
 | 
						|
	 * @SuppressWarnings(PHPMD.ShortMethodName)
 | 
						|
	 */
 | 
						|
	#[\ReturnTypeWillChange]
 | 
						|
	public function gc($maxLifeTime)
 | 
						|
	{
 | 
						|
		$del = $this->connection->delete($this->_sessionTable);
 | 
						|
		//$del->where()->lessThan(self::COLUMN_EXPIRES, time() - $maxLifeTime);
 | 
						|
		$del->where()->lessThan(self::COLUMN_EXPIRES, time());
 | 
						|
		return $del->load();
 | 
						|
	}
 | 
						|
}
 | 
						|
 |