So finally I cracked a way for it
The algorithm is like this
1.just prior to session_start, a cookie cookie_start_time
is set to current time. life time of this cookie will be same as that of session. Both uses the variable $this->cookieLifeTime
to set life time
setcookie($cookie_name/*cookie_start_time */, $cookie_value/*$this->cookieStartTime*/, time() + $this->cookieLifeTime /* (86400 * 30) */, "/"); // 86400 = 1 day
2. in session '_write' we will set that value to db table field cookie_start_time
same as $this->cookieStartTime
3. in session '_read' we do a check
if($getRowsOfSession[0]['cookie_start_time'] != $this->cookieStartTime).
if it returns true, that means this is a duplicate session and the user is redirected to destroy the session and again redirected to start a new session.(2 redirections total)
So Let us go to the scripts and database in detail
Set up data base table
<pre class="wp-block-syntaxhighlighter-code">DROP TABLE IF EXISTS <code>sessions_with_db</code>;
CREATE TABLE IF NOT EXISTS <code>sessions_with_db</code> (
<code>id</code> varchar(32) COLLATE utf8_unicode_ci NOT NULL,
<code>access</code> datetime NOT NULL,
<code>data</code> text COLLATE utf8_unicode_ci NOT NULL,
<code>cookie_start_time</code> datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
UNIQUE KEY <code>id</code> (<code>id</code>)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;</pre>
Session Handler Class MySessionHandler.php
to test the working change
replace $this->cookieLifeTime
in
setcookie($cookie_name, $cookie_value, time() + $this->cookieLifeTime
to 60 (1 minute)
Dont forget to set it back after testing 🙂
<pre class="wp-block-syntaxhighlighter-code"><?php
/***
https://stackoverflow.com/questions/36753513/how-do-i-save-php-session-data-to-a-database-instead-of-in-the-file-system
https://culttt.com/2013/02/04/how-to-save-php-sessions-to-a-database/
DROP TABLE IF EXISTS <code>sessions_with_db</code>;
CREATE TABLE IF NOT EXISTS <code>sessions_with_db</code> (
<code>id</code> varchar(32) COLLATE utf8_unicode_ci NOT NULL,
<code>access</code> datetime NOT NULL,
<code>data</code> text COLLATE utf8_unicode_ci NOT NULL,
<code>cookie_start_time</code> datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
UNIQUE KEY <code>id</code> (<code>id</code>)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
*/
/***
<?php
//testUniqueSessionWithCookie.php
ob_start();
require "MySessionHandler.php";
$site_options = array();
$site_options["debug_mode"] = true;
$testSessId = "";
$dbDetails = array();
$dbDetails['DB_USER'] = "root";
$dbDetails['DB_PASS'] = "";
$dbDetails['DB_SERVER'] = "localhost";
$dbDetails['DB_NAME'] = "tests";//http://localhost/phpmyadmin/db_structure.php?server=1&db=tests
MySessionHandler::checkIfDuplicateSession($dbDetails);
$HSH = new MySessionHandler($dbDetails);
print "<p>Hello this is an index page</p>";
$_SESSION['horses'] = "treesx3";
$_SESSION['tiespan'] = !isset($_SESSION['tiespan'])?0:((int)$_SESSION['tiespan']+7);
echo "tiespan after change is ".$_SESSION['tiespan']."<br />";
print "<p>There should be some session data in the database now. <a href='index3.php'>link</a></p>";
var_dump($_SESSION);
?>
***/
require_once "mysqli_db_class.php";
class MySessionHandler /* implements SessionHandlerInterface */ {
private $database = null;
//ALTER TABLE <code>sessions_with_db</code> ADD <code>cookie_start_time</code> DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' AFTER <code>data</code>;
private $cookieStartTime = null;
private $cookieLifeTime = 86400;//60 * 60 * 24;//60;
private $freshSession = false;
private $sessionIdName = null;
public static function checkIfDuplicateSession($dbDetails)
{
global $testSessId;
if(isset($_REQUEST['destroySession']) && $_REQUEST['destroySession'] == 'true')
{
//https://stackoverflow.com/questions/33517997/how-do-i-delete-phpsessid-on-client-computers
session_start(); // initialize session
//Warning: session_id(): Cannot change session id when session is active
/* $sessId = $testSessId;//session_id();
if($sessId !="")
{
session_id($sessId);
} */
$sessId = session_id();
session_destroy(); // destroy session
setcookie("PHPSESSID","",time()-3600,"/"); // delete session cookie
//delete from DB too
$database = MySQL::getInstance($dbDetails);
$database->connectdb();
//$this->_destroy($sessId);
$deleteSQL = "DELETE FROM sessions_with_db WHERE id = ? LIMIT 1";
//echo $deleteSQL. " " . $sessId."<br />";
if ($database->executePS($deleteSQL, "s",$sessId)) {
/* return true;
} else {
return false; */
}
//$js = "document.cookie = 'PHPSESSID=;Path=/cv;expires=Thu, 01 Jan 1970 00:00:01 GMT;';\n";// this is not working
$jsMessage = "alert('Session destroyed id: ".$sessId ." ');\r\n";//".$sess_id ."
//die ($jsMessage);
$js = $jsMessage . "window.location.href='testUniqueSessionWithCookie.php?destroySession=false'";
die ("<script>{$js}</script>");
//die("Session destroyed");
}//if(isset($_REQUEST['destroySession']))
}//public static function checkIfDuplicateSession()
public function __construct($dbDetails){
global $testSessId;
$user_tz = new DateTimeZone('Asia/Kolkata');//'America/New_York');
$user = new DateTime('now', $user_tz);
$this->cookieStartTime = $user->format('Y-m-d H:i:s');//date("Y-m-d H:i:s",time());
/***
* Just setting up my own database connection. Use yours as you need.
***/
$this->database = MySQL::getInstance($dbDetails);
$this->database->connectdb();
// Set handler to overide SESSION
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
$cookie_name = "cookie_start_time" ;
$cookie_value = $this->cookieStartTime;//date('Y-m-d H:i:s',time());
if(!isset($_COOKIE['cookie_start_time']))
{
setcookie($cookie_name, $cookie_value, time() + $this->cookieLifeTime /* (86400 * 30) */, "/"); // 86400 = 1 day
}
else
{
$this->cookieStartTime = $_COOKIE['cookie_start_time'];
}//if(!isset($_COOKIE['cookie_start_time']))
$sessId = $testSessId;//session_id();
if($sessId !="")
{
session_id($sessId);
}
session_start([//https://www.php.net/manual/en/function.session-start.php#example-5976
'cookie_lifetime' => $this->cookieLifeTime //86400,//60 * 60 * 24 //* 7 // 7 day cookie lifetime
]);
//$this->sessionIdName = session_name();
//echo "Session name is ".$this->sessionIdName." ".__LINE__."</br>";
/*
//https://stackoverflow.com/questions/37789172/php-session-randomly-dies-when-read-and-close-is-active
$SESSION = [
'name' => 'my_session_name',
'storage' => 'default',
'options' => [
'read_and_close' => true,
'cookie_lifetime' => false,
'use_strict_mode' => true,
'use_only_cookies' => 1,
'cookie_httponly' => 1,
'use_trans_sid' => 0,
//Ensure this is true for production:
'cookie_secure' => false
],
];
session_name($SESSION['name']);
session_start($SESSION['options']);*/
}
/**
* Open
*/
public function _open($savepath, $id){
global $site_options;if($site_options["debug_mode"])echo "</p><b> _open</b></p>";
// If successful
return !is_null($this->database);
// return true;
}
/**
* Read
*/
public function _read($id)
{
global $site_options;
if($site_options["debug_mode"])
{
if(isset($_COOKIE['cookie_start_time']))echo "cookie_start_time is ".$_COOKIE['cookie_start_time']."<br />";
else echo "cookie_start_time not set <br /> ";
echo "</p><b> _read</b></p>";
}//if($site_options["debug_mode"])
// Set query
$getRowsOfSession = $this->database->selectPS("SELECT <code>data</code>,<code>cookie_start_time</code> FROM sessions_with_db WHERE id = ? LIMIT 1","s",$id);
if(count($getRowsOfSession)== 1)
{
//check with $this->cookieStartTime
if($getRowsOfSession[0]['cookie_start_time'] != $this->cookieStartTime)
{
//echo "Session name is ".$this->sessionIdName." ".__LINE__."</br>";
//$alertText = " ". $getRowsOfSession[0]['cookie_start_time'] . " != ".$this->cookieStartTime;
$js = "alert('Same session id(".$this->sessionIdName.") exists ".$getRowsOfSession[0]['cookie_start_time'] . " " . $this->cookieStartTime."');\r\n";
$js .= "window.location.href='testUniqueSessionWithCookie.php?destroySession=true'";
die ("<script>{$js}</script>");
//header("location: testUniqueSessionWithCookie.php")
}//if($getRowsOfSession[0]['cookie_start_time'] != $this->cookieStartTime)
return $getRowsOfSession[0]['data'];
} else {
$this->freshSession = true;
return '';
}
}
/**
* Write
*/
public function _write($id, $data)
{
global $site_options;if($site_options["debug_mode"])echo "</p><b> _write</b></p>";
// Create time stamp
$access = time();
// Set query
$dataReplace[0] = $id;
//$dataReplace[1] = $access;
$dataReplace[1] = $data;
//if ($this->database->noReturnQuery('REPLACE INTO sessions_with_db(id,access,<code>data</code>) VALUES (?, ?, ?)', $dataReplace)) {
/*
REPLACE works exactly like INSERT , except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.
*/
//echo "REPLACE INTO sessions_with_db(id,access,<code>data</code>) VALUES ('{$id}', now(), '{$data}')";
/* date_default_timezone_set('Asia/Kolkata'); // IST
$max= 3600;
$old = time() - $max;
$dateOfOld = date('Y-m-d H:i:s',$old);
echo "DELETE FROM sessions_with_db WHERE access < '".$dateOfOld."'<br />"; */
if(/* $this->freshSession && */ $this->database->executePS('REPLACE INTO sessions_with_db(id,access,<code>data</code>,<code>cookie_start_time</code>) VALUES (?, now(), ?,?)',"sss",$id,$data,$this->cookieStartTime))
{
return true;
} else {
return false;
}
/* if($this->database->executePS('REPLACE INTO sessions_with_db(id,access,<code>data</code>,) VALUES (?, now(), ?)',"ss",$id,$data))
{
return true;
} else {
return false;
} */
}
/**
* Destroy
*/
public function _destroy($id)
{
global $site_options;if($site_options["debug_mode"])echo "</p><b> _destroy</b></p>";
// Set query
//if ($this->database->noReturnQuery('DELETE FROM sessions_with_db WHERE id = ? LIMIT 1', $id)) {
if ($this->database->executePS('DELETE FROM sessions_with_db WHERE id = ? LIMIT 1', "s",$id)) {
return true;
} else {
return false;
}
}
/**
* Close
*/
public function _close(){
global $site_options;if($site_options["debug_mode"])echo "</p><b> _close</b></p>";
// Close the database connection
//if($this->database->dbiLink->close){
if($this->database->disconnect()){
// Return True
return true;
}
// Return False
return false;
}
/**
* Garbage Collection
*/
public function _gc($max)
{
global $site_options;if($site_options["debug_mode"])echo "</p><b> _gc</b></p>";
// Calculate what is to be deemed old
date_default_timezone_set('Asia/Kolkata'); // IST
$old = time() - $max;
$dateOfOld = date('Y-m-d H:i:s',$old);
//echo "DELETE FROM sessions_with_db WHERE access < '".$dateOfOld."'<br />";
//if ($this->database->noReturnQuery('DELETE FROM sessions_with_db WHERE access < ?', $old)) {
if ($this->database->executePS('DELETE FROM sessions_with_db WHERE access < ?', "s",$dateOfOld)) {
return true;
} else {
return false;
}
}
public function __destruct()
{
global $site_options;if($site_options["debug_mode"])echo "</p><b> __destruct</b></p>";
$this->_close();
}
}
?></pre>
Finally testUniqueSessionWithCookie.php
<?php
ob_start();
require "MySessionHandler.php";
$site_options = array();
$site_options["debug_mode"] = true;
$testSessId = "";
$dbDetails = array();
$dbDetails['DB_USER'] = "root";
$dbDetails['DB_PASS'] = "";
$dbDetails['DB_SERVER'] = "localhost";
$dbDetails['DB_NAME'] = "tests";//http://localhost/phpmyadmin/db_structure.php?server=1&db=tests
MySessionHandler::checkIfDuplicateSession($dbDetails);
$HSH = new MySessionHandler($dbDetails);
print "<p>Hello this is an index page</p>";
$_SESSION['horses'] = "treesx3";
$_SESSION['tiespan'] = !isset($_SESSION['tiespan'])?0:((int)$_SESSION['tiespan']+7);
echo "tiespan after change is ".$_SESSION['tiespan']."<br />";
print "<p>There should be some session data in the database now. <a href='index3.php'>link</a></p>";
var_dump($_SESSION);
?>
mysqli_db_class.php is a utility class used in session handler
<?php
/**
*Class that could simplify mysql database operations.Used in attached page-nav class
*
*
https://www.w3schools.com/php/php_mysql_prepared_statements.asp
Sample
$dbDetails['DB_USER'] = username;
$dbDetails['DB_PASS'] = password;
$dbDetails['DB_SERVER'] = server;
$dbDetails['DB_NAME'] = database;
$db = new MySQL($dbDetails);
$db->connectdb();
//select query usage
$sql = "SELECT * FROM updates ";
$latest_updates = $db->select_sql($sql);
$row_latest_updates = $latest_updates[0];
$totalRows_latest_updates = count($latest_updates);
//other querie usage
$SQL="delete fromcurrent_users where DATE_ADD(last_visited,INTERVAL 1 year)<NOW()";
$db->execute_sql($SQL);
*
* @package database and pagenav class
* @author Sreekanth Dayanand <brownbedi@gmail.com>
* @version 1.0 <2009/09/18>
* @copyright GNU General Public License (GPL)
**/
class MySQL {
var $user;
var $pass;
var $server;
var $db;
var $conn;
var $query;
var $connected = false;
private static $inst = null;
public static function getInstance($dbDetails)
{
/*
if(ClassPageConfig::$inst == null)
{
ClassPageConfig::$inst = new ClassPageConfig();
}//if(ClassPageConfig::$inst == null)
return ClassPageConfig::$inst;
*/
//https://www.php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php &
//https://refactoring.guru/design-patterns/singleton/php/example
$ref = new \ReflectionClass( get_called_class() ) ;
$reflectionProperty = new \ReflectionProperty(static::class, 'inst');
$reflectionProperty->setAccessible(true);
//echo $reflectionProperty->getValue();
$inst = $reflectionProperty->getValue();;//$reflectedClass->getStaticPropertyValue('inst');
if ( $inst == null)
{
// The magic.
//$ctor->setAccessible( true ) ;
//$inst = new static();
$inst = new static($dbDetails);
//echo "INSTANTIATED ".print_r($inst,true) ."<br />";
$reflectionProperty->setValue(null/* null for static var */, $inst);
}
return $inst ;
}
//--------------------------------------------------------
private function __construct($dbDetails)//MySQL(
//--------------------------------------------------------
{
$this->user = $dbDetails['DB_USER'];
$this->pass = $dbDetails['DB_PASS'];
$this->server = $dbDetails['DB_SERVER'];
$this->db = $dbDetails['DB_NAME'];
}
//--------------------------------------------------------
function connectdb()
//--------------------------------------------------------
{
// Create connection
$this->conn = new mysqli($this->server, $this->user, $this->pass, $this->db);// or die("Failed to connect to database".$this->db);
// Check connection
if ($this->conn->connect_error) {
die("Connection failed: " . $this->conn->connect_error);
$this->connected = true;
}
else{
//die("Connection success");
}
}
function getVersion()
{
return $this->conn->server_info;;
}
//--------------------------------------------------------
function disconnect()
//--------------------------------------------------------
{
/* echo "Disconnect called<br />";
throw new Exception(); */
if($this->connected)$this->conn->close();
$this->connected = false;
}
//--------------------------------------------------------
//https://www.javatpoint.com/php-variable-length-argument-function
//function setquery($sql,...$bindArgs)
function executePS($sql,$types="",...$bindArgs)
//--------------------------------------------------------
{ global $site_options;
$this->query=$sql;
$stmt = $this->conn->prepare($sql) or die(sprintf("Error: %s.\n", $this->conn->error)."<br /> ".$sql."<br /> types :{$types }<br /> bindArgs <pre>".print_r($bindArgs,true)."</pre>");
if(count($bindArgs)>0)//https://stackoverflow.com/questions/17226762/mysqli-bind-param-for-array-of-strings
{
//$types = str_repeat('s', count($bindArgs)); //types
$stmt->bind_param($types, ...$bindArgs); // bind array at once
}//if(count($bindArgs)>0)
//Error: 1062 : Duplicate entry '...' for key 'link_is_unique'.
if($stmt->execute())// or die(sprintf("Error: %s.\n", $stmt->errno ." : ". $stmt->error)."<br /> ".$sql."<br /> types :{$types }<br /> bindArgs <pre>".print_r($bindArgs,true)."</pre>");
{
if($site_options["debug_mode"]) echo $sql."<br />";
$stmt->close();
$resultGot = array("status"=> "success","insert_id" => $this->lastInsertId());
}
else //if($stmt->execute())
{
$resultGot = array("status"=> "failed","error_no" => $stmt->errno , "error_desc" => $stmt->error);
}//if($stmt->execute())
return $resultGot;
}
/**
*returns the resultset as an array
*@access public
*@param integer height
*@return boolean
**/
//----------------------------------------------------------------------------
function selectPS($sql,$types="",...$bindArgs)
//----------------------------------------------------------------------------
{
global $site_options;
$this->query=$sql;
$stmt = $this->conn->prepare($sql);
if(count($bindArgs)>0)//https://stackoverflow.com/questions/17226762/mysqli-bind-param-for-array-of-strings
{
//$types = str_repeat('s', count($bindArgs)); //types
$stmt->bind_param($types, ...$bindArgs); // bind array at once
}//if(count($bindArgs)>0)
$stmt->execute() or die(sprintf("Error: %s.\n", $stmt->errno ." : ". $stmt->error)."<br /> ".$sql."<br /> types :{$types }<br /> bindArgs <pre>".print_r($bindArgs,true)."</pre>");
if($site_options["debug_mode"]) echo $sql."<br />";
$ret = array();
// Call to undefined method mysqli_stmt::get_result()
/* $result = $stmt->get_result();
//echo "<pre>".print_r($result,true)."</pre>";
if($result->num_rows >0){
while( $row = $result->fetch_assoc())
{
$ret[] = $row;
}
} */
$stmt->store_result();
while($assoc_array = $this->fetchAssocStatement($stmt))
{
$ret[] = $assoc_array;
}//while($assoc_array = $this->fetchAssocStatement($stmt))
$stmt->close();
//die("<pre>".print_r($ret,true)."</pre>");
return $ret;
}//function selectPS($sql,...$bindArgs)
//https://stackoverflow.com/questions/8321096/call-to-undefined-method-mysqli-stmtget-result
function fetchAssocStatement($stmt)
{
//die("num rows is " . $stmt->num_rows);
if($stmt->num_rows>0)
{
$result = array();
$md = $stmt->result_metadata();
$params = array();
while($field = $md->fetch_field()) {
$params[] = &$result[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $params);
if($stmt->fetch())
//die("<pre>".print_r($result,true)."</pre>");
return $result;
}
return null;
}
//For insert, update, delete
//--------------------------------------------------------
function sqlnodie()
//--------------------------------------------------------
{
mysql_query($this->query);
}
function lastInsertId()
{
return $this->conn->insert_id;
}
}
?>
|
|