SetCookieDomain('my.domain.com');
$session->SetCookiePath('/myscript');
$session->SetCookieName('my_sid_cookie');
$session->SetGETName('sid');
$session->InitSession();
...
//link output:
echo "NeedQueryString() ? 'sid='.$session->SID : '' ) .">My Link";
*/
class Session extends kBase {
const smAUTO = 1;
const smCOOKIES_ONLY = 2;
const smGET_ONLY = 3;
const smCOOKIES_AND_GET = 4;
var $Checkers;
var $Mode;
var $OriginalMode = null;
var $GETName = 'sid';
var $CookiesEnabled = true;
var $CookieName = 'sid';
var $CookieDomain;
var $CookiePath;
var $CookieSecure = 0;
var $SessionTimeout = 3600;
var $Expiration;
var $SID;
var $CachedSID;
var $SessionSet = false;
/**
* Session ID is used from GET
*
* @var bool
*/
var $_fromGet = false;
/**
* Enter description here...
*
* @var SessionStorage
*/
var $Storage;
var $CachedNeedQueryString = null;
/**
* Session Data array
*
* @var Params
*/
var $Data;
/**
* Names of optional session keys with their optional values (which does not need to be always stored)
*
* @var Array
*/
var $OptionalData = Array ();
/**
* Session expiration mark
*
* @var bool
*/
var $expired = false;
/**
* Creates session
*
* @param int $mode
* @access public
*/
public function __construct($mode = self::smAUTO)
{
parent::__construct();
$this->SetMode($mode);
}
function SetMode($mode)
{
$this->Mode = $mode;
$this->CachedNeedQueryString = null;
$this->CachedSID = null;
}
function SetCookiePath($path)
{
$this->CookiePath = str_replace(' ', '%20', $path);
}
/**
* Setting cookie domain. Set false for local domains, because they don't contain dots in their names.
*
* @param string $domain
*/
function SetCookieDomain($domain)
{
// 1. localhost or other like it without "." in domain name
if (!substr_count($domain, '.')) {
// don't use cookie domain at all
$this->CookieDomain = false;
return ;
}
// 2. match using predefined cookie domains from configuration
$cookie_domains = $this->Application->ConfigValue('SessionCookieDomains');
if ($cookie_domains) {
$cookie_domains = array_map('trim', explode("\n", $cookie_domains));
foreach ($cookie_domains as $cookie_domain) {
if (ltrim($cookie_domain, '.') == $domain) {
$this->CookieDomain = $cookie_domain; // as defined in configuration
return ;
}
}
}
// 3. only will execute, when none of domains were matched at previous step
$this->CookieDomain = $this->_autoGuessDomain($domain);
}
/**
* Auto-guess cookie domain based on $_SERVER['HTTP_HOST']
*
* @param $domain
* @return string
*/
function _autoGuessDomain($domain)
{
static $cache = Array ();
if (!array_key_exists($domain, $cache)) {
switch ( substr_count($domain, '.') ) {
case 2:
// 3rd level domain (3 parts)
$cache[$domain] = substr($domain, strpos($domain, '.')); // with leading "."
break;
case 1:
// 2rd level domain (2 parts)
$cache[$domain] = '.' . $domain; // with leading "."
break;
default:
// more then 3rd level
$cache[$domain] = ltrim($domain, '.'); // without leading "."
break;
}
}
return $cache[$domain];
}
function SetGETName($get_name)
{
$this->GETName = $get_name;
}
function SetCookieName($cookie_name)
{
$this->CookieName = $cookie_name;
}
function InitStorage($special)
{
$this->Storage =& $this->Application->recallObject('SessionStorage.'.$special);
$this->Storage->setSession($this);
}
public function Init($prefix, $special)
{
parent::Init($prefix, $special);
$this->CheckIfCookiesAreOn();
if ($this->CookiesEnabled) $_COOKIE['cookies_on'] = 1;
$this->Checkers = Array();
$this->InitStorage($special);
$this->Data = new Params();
$tmp_sid = $this->GetPassedSIDValue();
$check = $this->Check();
if ($this->Application->isAdmin) {
// 1. Front-End session may not be created (SID is present, but no data in database).
// Check expiration LATER from kApplication::Init, because template, used in session
// expiration redirect should be retrieved from mod-rewrite url first.
// 2. Admin sessions are always created, so case when SID is present,
// but session in database isn't is 100% session expired. Check expiration
// HERE because Session::SetSession will create missing session in database
// and when Session::ValidateExpired will be called later from kApplication::Init
// it won't consider such session as expired !!!
$this->ValidateExpired();
}
if ($check) {
$this->SID = $this->GetPassedSIDValue();
$this->Refresh();
$this->LoadData();
}
else {
$this->SetSession();
}
if (!is_null($this->OriginalMode)) $this->SetMode($this->OriginalMode);
}
function ValidateExpired()
{
if (defined('IS_INSTALL') && IS_INSTALL) {
return ;
}
// $this->DeleteExpired(); // called from u:OnDeleteExpiredSessions agent now
if ($this->expired || ($this->CachedSID && !$this->_fromGet && !$this->SessionSet)) {
$this->RemoveSessionCookie();
// true was here to force new session creation, but I (kostja) used
// RemoveCookie a line above, to avoid redirect loop with expired sid
// not being removed setSession with true was used before, to set NEW
// session cookie
$this->SetSession();
// case #1: I've OR other site visitor expired my session
// case #2: I have no session in database, but SID is present
$this->expired = false;
$expire_event = new kEvent('u:OnSessionExpire');
$this->Application->HandleEvent($expire_event);
}
}
/**
* This is redirect from https to http or via versa
*
* @return bool
*/
function IsHTTPSRedirect()
{
$http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;
return (
( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
||
( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
);
}
/**
* Helper method for detecting cookie availability
*
* @return bool
*/
function _checkCookieReferer()
{
// removing /admin for compatability with in-portal (in-link/admin/add_link.php)
$path = preg_replace('/admin[\/]{0,1}$/', '', $this->CookiePath);
$reg = '#^'.preg_quote(PROTOCOL.ltrim($this->CookieDomain, '.').$path).'#';
return preg_match($reg, getArrayValue($_SERVER, 'HTTP_REFERER') );
}
function CheckIfCookiesAreOn()
{
if ($this->Mode == self::smGET_ONLY) {
//we don't need to bother checking if we would not use it
$this->CookiesEnabled = false;
return;
}
$http_query =& $this->Application->recallObject('HTTPQuery');
$cookies_on = array_key_exists('cookies_on', $http_query->Cookie); // not good here
$get_sid = getArrayValue($http_query->Get, $this->GETName);
if ($this->IsHTTPSRedirect() && $get_sid) { // Redirect from http to https on different domain
$this->OriginalMode = $this->Mode;
$this->SetMode(self::smGET_ONLY);
}
if (!$cookies_on || $this->IsHTTPSRedirect()) {
//If referer is our server, but we don't have our cookies_on, it's definetly off
$is_install = defined('IS_INSTALL') && IS_INSTALL;
if (!$is_install && $this->_checkCookieReferer() && !$this->Application->GetVar('admin') && !$this->IsHTTPSRedirect()) {
$this->CookiesEnabled = false;
}
else {
//Otherwise we still suppose cookies are on, because may be it's the first time user visits the site
//So we send cookies on to get it next time (when referal will tell us if they are realy off
$this->SetCookie('cookies_on', 1, adodb_mktime() + 31104000); //one year should be enough
}
}
else {
$this->CookiesEnabled = true;
}
return $this->CookiesEnabled;
}
/**
* Sets cookie for current site using path and domain
*
* @param string $name
* @param mixed $value
* @param int $expires
*/
function SetCookie($name, $value, $expires = null)
{
if (isset($expires) && $expires < adodb_mktime()) {
unset($this->Application->HttpQuery->Cookie[$name]);
}
else {
$this->Application->HttpQuery->Cookie[$name] = $value;
}
$old_style_domains = Array (
// domain like in pre 5.1.0 versions
'.' . SERVER_NAME,
// auto-guessed domain (when user specified other domain in configuration variable)
$this->_autoGuessDomain(SERVER_NAME)
);
foreach ($old_style_domains as $old_style_domain) {
if ($this->CookieDomain != $old_style_domain) {
// new style cookie domain -> delete old style cookie to prevent infinite redirect
setcookie($name, $value, adodb_mktime() - 3600, $this->CookiePath, $old_style_domain, $this->CookieSecure);
}
}
setcookie($name, $value, $expires, $this->CookiePath, $this->CookieDomain, $this->CookieSecure);
}
function Check()
{
// don't check referer here, because it doesn't provide any security option and can be easily falsified
$sid = $this->GetPassedSIDValue();
if (empty($sid)) {
return false;
}
//try to load session by sid, if everything is fine
$result = $this->LoadSession($sid);
$this->SessionSet = $result; // fake front-end session will given "false" here
return $result;
}
function LoadSession($sid)
{
if( $this->Storage->LocateSession($sid) ) {
// if we have session with such SID - get its expiration
$this->Expiration = $this->Storage->GetExpiration();
// If session has expired
if ($this->Expiration < adodb_mktime()) {
// when expired session is loaded, then SID is
// not assigned, but used in Destroy method
$this->SID = $sid;
$this->Destroy();
$this->expired = true;
// when Destory methods calls SetSession inside and new session get created
return $this->SessionSet;
}
// Otherwise it's ok
return true;
}
else {
// fake or deleted due to expiration SID
if (!$this->_fromGet) {
$this->expired = true;
}
return false;
}
}
function GetPassedSIDValue($use_cache = 1)
{
if (!empty($this->CachedSID) && $use_cache) {
return $this->CachedSID;
}
$http_query =& $this->Application->recallObject('HTTPQuery');
$get_sid = getArrayValue($http_query->Get, $this->GETName);
$sid_from_get = $get_sid ? true : false;
if ($this->Application->GetVar('admin') == 1 && $get_sid) {
$sid = $get_sid;
}
else {
switch ($this->Mode) {
case self::smAUTO:
//Cookies has the priority - we ignore everything else
$sid = $this->CookiesEnabled ? $this->GetSessionCookie() : $get_sid;
if ($this->CookiesEnabled) {
$sid_from_get = false;
}
break;
case self::smCOOKIES_ONLY:
$sid = $this->GetSessionCookie();
break;
case self::smGET_ONLY:
$sid = $get_sid;
break;
case self::smCOOKIES_AND_GET:
$cookie_sid = $this->GetSessionCookie();
//both sids should match if cookies are enabled
if (!$this->CookiesEnabled || ($cookie_sid == $get_sid)) {
$sid = $get_sid; //we use get here just in case cookies are disabled
}
else {
$sid = '';
$sid_from_get = false;
}
break;
}
}
$this->CachedSID = $sid;
$this->_fromGet = $sid_from_get;
return $this->CachedSID;
}
/**
* Returns session id
*
* @return int
* @access public
*/
function GetID()
{
return $this->SID;
}
/**
* Generates new session id
*
* @return int
* @access private
*/
function GenerateSID()
{
list ($usec, $sec) = explode(' ', microtime());
$sid_part_1 = substr($usec, 4, 4);
$sid_part_2 = mt_rand(1, 9);
$sid_part_3 = substr($sec, 6, 4);
$digit_one = substr($sid_part_1, 0, 1);
if ($digit_one == 0) {
$digit_one = mt_rand(1, 9);
$sid_part_1 = preg_replace('/^0/', '', $sid_part_1);
$sid_part_1 = $digit_one . $sid_part_1;
}
$this->setSID($sid_part_1 . $sid_part_2 . $sid_part_3);
return $this->SID;
}
/**
* Set's new session id
*
* @param int $new_sid
* @access private
*/
function setSID($new_sid)
{
$this->SID /*= $this->CachedSID*/ = $new_sid; // don't set cached sid here
$this->Application->SetVar($this->GETName,$new_sid);
}
function NeedSession()
{
$data = $this->Data->GetParams();
$data_keys = array_keys($data);
$optional_keys = array_keys($this->OptionalData);
$real_keys = array_diff($data_keys, $optional_keys);
return $real_keys ? true : false;
}
function SetSession($force = false)
{
if ($this->SessionSet && !$force) {
return true;
}
if (!$force && !($this->Application->isAdmin || $this->Application->GetVar('admin')) && !$this->NeedSession()) {
// don't create session (in db) on Front-End, when sid is present (GPC), but data in db isn't
if ($this->_fromGet) {
// set sid, that was given in GET
$this->setSID( $this->GetPassedSIDValue() );
} else {
// re-generate sid only, when cookies are used
$this->GenerateSID();
}
return false;
}
if (!$this->SID || $force) {
$this->GenerateSID();
}
$this->Expiration = adodb_mktime() + $this->SessionTimeout;
switch ($this->Mode) {
case self::smAUTO:
if ($this->CookiesEnabled) {
$this->SetSessionCookie();
}
break;
case self::smGET_ONLY:
break;
case self::smCOOKIES_ONLY:
case self::smCOOKIES_AND_GET:
$this->SetSessionCookie();
break;
}
$this->Storage->StoreSession();
if ($this->Application->isAdmin || $this->Special == 'admin') {
$this->StoreVar('admin', 1);
}
$this->SessionSet = true; // should be called before SaveData, because SaveData will try to SetSession again
if ($this->Special != '') {
// front-session called from admin or otherwise, then save it's data
$this->SaveData();
}
$this->Application->resetCounters('UserSession');
return true;
}
/**
* Returns SID from cookie.
*
* Use 2 cookies to have 2 expiration:
* - 1. for normal expiration when browser is not closed (30 minutes by default), configurable
* - 2. for advanced expiration when browser is closed
*
* @return int
*/
function GetSessionCookie()
{
$keep_session_on_browser_close = $this->Application->ConfigValue('KeepSessionOnBrowserClose');
if (isset($this->Application->HttpQuery->Cookie[$this->CookieName]) &&
( $keep_session_on_browser_close ||
(
!$keep_session_on_browser_close &&
isset($this->Application->HttpQuery->Cookie[$this->CookieName.'_live'])
&&
$this->Application->HttpQuery->Cookie[$this->CookieName] == $this->Application->HttpQuery->Cookie[$this->CookieName.'_live']
)
)
) {
return $this->Application->HttpQuery->Cookie[$this->CookieName];
}
return false;
}
/**
* Updates SID in cookie with new value
*
*/
function SetSessionCookie()
{
$this->SetCookie($this->CookieName, $this->SID, $this->Expiration);
$this->SetCookie($this->CookieName.'_live', $this->SID);
$_COOKIE[$this->CookieName] = $this->SID; // for compatibility with in-portal
}
function RemoveSessionCookie()
{
$this->SetCookie($this->CookieName, '');
$this->SetCookie($this->CookieName.'_live', '');
$_COOKIE[$this->CookieName] = null; // for compatibility with in-portal
}
/**
* Refreshes session expiration time
*
* @access private
*/
function Refresh()
{
if ($this->Application->GetVar('skip_session_refresh')) {
return ;
}
if ($this->CookiesEnabled) {
// we need to refresh the cookie
$this->SetSessionCookie();
}
$this->Storage->UpdateSession();
}
function Destroy()
{
$this->Storage->DeleteSession();
$this->Data = new Params();
$this->SID = $this->CachedSID = '';
$this->SessionSet = false;
if ($this->CookiesEnabled) {
$this->SetSessionCookie(); //will remove the cookie due to value (sid) is empty
}
$this->SetSession(true); //will create a new session, true to force
}
function NeedQueryString($use_cache = 1)
{
if ($this->CachedNeedQueryString != null && $use_cache) {
return $this->CachedNeedQueryString;
}
$result = false;
switch ($this->Mode) {
case self::smAUTO:
if (!$this->CookiesEnabled) {
$result = true;
}
break;
/*case self::smCOOKIES_ONLY:
break;*/
case self::smGET_ONLY:
case self::smCOOKIES_AND_GET:
$result = true;
break;
}
$this->CachedNeedQueryString = $result;
return $result;
}
function LoadData()
{
$this->Data->AddParams( $this->Storage->LoadData() );
}
function PrintSession($comment = '')
{
if (defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_SESSIONDATA')) {
// dump session data
$this->Application->Debugger->appendHTML('SessionStorage [' . ($this->RecallVar('admin') == 1 ? 'Admin' : 'Front-End') . '] ('.$comment.'):');
$session_data = $this->Data->GetParams();
ksort($session_data);
foreach ($session_data as $session_key => $session_value) {
if (kUtil::IsSerialized($session_value)) {
$session_data[$session_key] = unserialize($session_value);
}
}
$this->Application->Debugger->dumpVars($session_data);
if (!$this->RecallVar('admin')) {
// dump real keys (only for front-end)
$data_keys = array_keys($session_data);
$optional_keys = array_keys($this->OptionalData);
$real_keys = array_diff($data_keys, $optional_keys);
if ($real_keys) {
$ret = '';
foreach ($real_keys as $real_key) {
$ret .= '[' . $real_key . '] = [' . $session_data[$real_key] . ']
';
}
$this->Application->Debugger->appendHTML('Real Keys:
' . $ret);
}
}
}
if (defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_PERSISTENTDATA')) {
// dump persistent session data
if ($this->Storage->PersistentVars) {
$this->Application->Debugger->appendHTML('Persistant Session:');
$session_data = $this->Storage->PersistentVars;
ksort($session_data);
foreach ($session_data as $session_key => $session_value) {
if (kUtil::IsSerialized($session_value)) {
$session_data[$session_key] = unserialize($session_value);
}
}
$this->Application->Debugger->dumpVars($session_data);
}
}
}
function SaveData($params = Array ())
{
if (!$this->SetSession()) { // call it here - it may be not set before, because there was no need; if there is a need, it will be set here
return;
}
if (!$this->Application->GetVar('skip_last_template') && $this->Application->GetVar('ajax') != 'yes') {
$this->SaveLastTemplate( $this->Application->GetVar('t'), $params );
}
$this->PrintSession('after save');
$this->Storage->SaveData();
}
/**
* Save last template
*
* @param string $t
* @param Array $params
*/
function SaveLastTemplate($t, $params = Array ())
{
$wid = $this->Application->GetVar('m_wid');
$last_env = $this->getLastTemplateENV($t, Array('m_opener' => 'u'));
$last_template = basename($_SERVER['PHP_SELF']).'|'.mb_substr($last_env, mb_strlen(ENV_VAR_NAME) + 1);
$this->StoreVar(rtrim('last_template_'.$wid, '_'), $last_template);
// prepare last_template for opener stack, module & session could be added later
$last_env = $this->getLastTemplateENV($t, null, false);
$last_template = basename($_SERVER['PHP_SELF']).'|'.mb_substr($last_env, mb_strlen(ENV_VAR_NAME) + 1);
// save last_template in persistant session
if (!$wid) {
if ($this->Application->isAdmin) {
// only for main window, not popups, not login template, not temp mode (used in adm:MainFrameLink tag)
$temp_mode = false;
$passed = explode(',', $this->Application->GetVar('passed'));
foreach ($passed as $passed_prefix) {
if ($this->Application->GetVar($passed_prefix.'_mode')) {
$temp_mode = true;
break;
}
}
if (!$temp_mode) {
if (isset($this->Application->HttpQuery->Get['section'])) {
// check directly in GET, bacause LinkVar (session -> request) used on these vars
$last_template .= '§ion='.$this->Application->GetVar('section').'&module='.$this->Application->GetVar('module');
}
$this->StorePersistentVar('last_template_popup', $last_template);
}
}
elseif ($this->Application->GetVar('admin')) {
// admin checking by session data to prevent recursive session save
static $admin_saved = null;
if (!$this->RecallVar('admin') && !isset($admin_saved)) {
// bug: we get recursion in this place, when cookies are disabled in browser and we are browsing
// front-end in admin's frame (front-end session is initialized using admin's sid and they are
// mixed together)
$admin_saved = true;
$admin_session =& $this->Application->recallObject('Session.admin');
/* @var $admin_session Session */
// save to admin last_template too, because when F5 is pressed in frameset Front-End frame should reload as well
$admin_session->StoreVar('last_template_popup', '../' . $last_template);
$admin_session->StorePersistentVar('last_template_popup', '../' . $last_template);
$admin_session->SaveData( Array ('save_last_template' => false) );
}
else {
// don't allow admin=1 & editing_mode=* to get in admin last_template
$last_template = preg_replace('/&(admin|editing_mode)=[\d]/', '', $last_template);
}
}
}
// save other last... variables for mistical purposes (customizations may be)
$this->StoreVar('last_url', $_SERVER['REQUEST_URI']); // needed by ord:StoreContinueShoppingLink
$this->StoreVar('last_env', mb_substr($last_env, mb_strlen(ENV_VAR_NAME)+1));
$save_last_template = array_key_exists('save_last_template', $params) ? $params['save_last_template'] : true;
if ($save_last_template) {
// save last template here, becase section & module could be added before
$this->StoreVar(rtrim('last_template_popup_'.$wid, '_'), $last_template);
}
}
function getLastTemplateENV($t, $params = null, $encode = true)
{
if (!isset($params)) {
$params = Array ();
}
$params['__URLENCODE__'] = 1; // uses "&" instead of "&" for url part concatenation + replaces "\" to "%5C" (works in HTML)
if ($this->Application->GetVar('admin') && !array_key_exists('admin', $params) && !defined('EDITING_MODE')) {
$params['editing_mode'] = ''; // used in kApplication::Run
}
$params = array_merge($this->Application->getPassThroughVariables($params), $params);
$ret = $this->Application->BuildEnv($t, $params, 'all');
if (!$encode) {
// cancels 2nd part of replacements, that URLENCODE does
$ret = str_replace('%5C', '\\', $ret);
}
return $ret;
}
function StoreVar($name, $value, $optional = false)
{
$this->Data->Set($name, $value);
if ($optional) {
// make variable optional, also remember optional value
$this->OptionalData[$name] = $value;
}
elseif (!$optional && array_key_exists($name, $this->OptionalData)) {
if ($this->OptionalData[$name] == $value) {
// same value as optional -> don't remove optional mark
return ;
}
// make variable non-optional
unset($this->OptionalData[$name]);
}
}
function StorePersistentVar($name, $value, $optional = false)
{
$this->Storage->StorePersistentVar($name, $value, $optional);
}
function LoadPersistentVars()
{
$this->Storage->LoadPersistentVars();
}
function StoreVarDefault($name, $value, $optional=false)
{
$tmp = $this->RecallVar($name);
if($tmp === false || $tmp == '')
{
$this->StoreVar($name, $value, $optional);
}
}
function RecallVar($name, $default = false)
{
$ret = $this->Data->Get($name);
return ($ret === false) ? $default : $ret;
}
function RecallPersistentVar($name, $default = false)
{
return $this->Storage->RecallPersistentVar($name, $default);
}
function RemoveVar($name)
{
$this->Storage->RemoveFromData($name);
$this->Data->Remove($name);
}
function RemovePersistentVar($name)
{
return $this->Storage->RemovePersistentVar($name);
}
/**
* Ignores session varible value set before
*
* @param string $name
*/
function RestoreVar($name)
{
$value = $this->Storage->GetFromData($name, '__missing__');
if ($value === '__missing__') {
// there is nothing to restore (maybe session was not saved), look in optional variable values
$value = array_key_exists($name, $this->OptionalData) ? $this->OptionalData[$name] : false;
}
return $this->StoreVar($name, $value);
}
function GetField($var_name, $default = false)
{
return $this->Storage->GetField($var_name, $default);
}
function SetField($var_name, $value)
{
$this->Storage->SetField($var_name, $value);
}
/**
* Deletes expired sessions
*
* @return Array expired sids if any
* @access private
*/
function DeleteExpired()
{
return $this->Storage->DeleteExpired();
}
/**
* Deletes given sessions
*
* @return Array
* @access private
*/
function DeleteSessions($session_ids, $delete_reason = SESSION_LOG_EXPIRED)
{
return $this->Storage->DeleteSessions($session_ids, $delete_reason);
}
/**
* Allows to check if user in this session is logged in or not
*
* @return bool
*/
function LoggedIn()
{
$user_id = $this->RecallVar('user_id');
$ret = $user_id > 0;
if (($this->RecallVar('admin') == 1 || defined('ADMIN')) && ($user_id == USER_ROOT)) {
$ret = true;
}
return $ret;
}
}