<?php
/**
* This file implements the Session class and holds the
* {@link session_unserialize_callback()} function used by it.
*
* A session can be bound to a user and provides functions to store data in its
* context.
* All Hitlogs are also bound to a Session.
*
* This file is part of the evoCore framework - {@link http://evocore.net/}
* See also {@link https://github.com/b2evolution/b2evolution}.
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
* Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
*
* @package evocore
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* A session tracks a given user (not necessarily logged in) while he's navigating the site.
* A session also stores data for the length of the session.
*
* Sessions are tracked with a cookie containing the session ID.
* The cookie also contains a random key to prevent sessions hacking.
*
* @package evocore
*/
class Session
{
/**
* The ID of the session.
* @var integer
*/
var $ID;
/**
* The session key (to be used in URLs).
* @var string
*/
var $key;
/**
* The user ID for the user of the session (NULL for anonymous (not logged in) user).
*
* @var integer
*/
var $user_ID;
/**
* Is the session validated?
* This means that it was created from a received cookie.
* @var boolean
*/
var $is_validated = false;
/**
* Session start timestamp
* @var string
*/
var $start_ts;
/**
* Session last seen timestamp which was logged
* Value may be off by up to 60 seconds
* @var string
*/
var $lastseen_ts;
/**
* Data stored for the session.
*
* This holds an array( expire, value ) for each data item key.
*
* @access protected
* @var array
*/
var $_data;
var $_session_needs_save = false;
/**
* The user device from where this session was created
*
* @var string
*/
var $sess_device;
/**
* Constructor
*
* If valid session cookie received or param session ID is provided: pull session from DB
* Otherwise, INSERT a session into DB
*
* @param integer Session ID
*/
function __construct( $session_ID = NULL )
{
global $DB, $Debuglog, $localtimenow, $Messages, $Settings, $UserSettings;
global $Hit;
global $cookie_session, $cookie_expires;
$Debuglog->add( 'Session: cookie_domain='.get_cookie_domain(), 'request' );
$Debuglog->add( 'Session: cookie_path='.get_cookie_path(), 'request' );
$timeout_sessions = NULL;
if( $this->user_ID != NULL )
{ // User is not anonymous, get custom session timeout (may return NULL):
$timeout_sessions = $UserSettings->get( 'timeout_sessions', $this->user_ID );
}
if( empty( $timeout_sessions ) )
{ // User is anonymous or has no custom session timeout. So, we use default session timeout:
$timeout_sessions = $Settings->get( 'timeout_sessions' );
}
if( $session_ID !== NULL )
{ // Get Session by requested ID:
$Debuglog->add( 'Session: ID='.$session_ID, 'request' );
$session_SQL = new SQL( 'Get session data by ID#'.$session_ID );
$session_SQL->SELECT( 'sess_ID, sess_key, sess_data, sess_user_ID, sess_start_ts, sess_lastseen_ts, sess_device' );
$session_SQL->FROM( 'T_sessions' );
$session_SQL->WHERE( 'sess_ID = '.$DB->quote( $session_ID ) );
$session_SQL->WHERE_and( 'UNIX_TIMESTAMP(sess_lastseen_ts) > '.( $localtimenow - $timeout_sessions ) );
$row = $DB->get_row( $session_SQL );
if( empty( $row ) )
{
$Debuglog->add( 'Session: Session by ID is invalid!', 'request' );
}
}
else
{ // Get Session from cookie data ID + key:
$session_cookie = param_cookie( $cookie_session, 'string', '' );
if( empty( $session_cookie ) )
{
$Debuglog->add( 'Session: No session cookie received.', 'request' );
}
elseif( ! preg_match( '~^(\d+)_(\w+)$~', $session_cookie, $match ) )
{
$Debuglog->add( 'Session: Invalid session cookie format!', 'request' );
}
else
{ // We have a valid session cookie:
$session_id_by_cookie = $match[1];
$session_key_by_cookie = $match[2];
$Debuglog->add( 'Session: Session ID received from cookie: '.$session_id_by_cookie, 'request' );
$row = $DB->get_row( '
SELECT sess_ID, sess_key, sess_data, sess_user_ID, sess_start_ts, sess_lastseen_ts, sess_device
FROM T_sessions
WHERE sess_ID = '.$DB->quote( $session_id_by_cookie ).'
AND sess_key = '.$DB->quote( $session_key_by_cookie ).'
AND UNIX_TIMESTAMP(sess_lastseen_ts) > '.( $localtimenow - $timeout_sessions ) );
if( empty( $row ) )
{
$Debuglog->add( 'Session: Session ID/key combination is invalid!', 'request' );
}
}
}
if( ! empty( $row ) )
{ // Load session data if row is found in DB:
$Debuglog->add( 'Session: Session ID is valid.', 'request' );
$this->ID = $row->sess_ID;
$this->key = $row->sess_key;
$this->user_ID = $row->sess_user_ID;
$this->start_ts = mysql2timestamp( $row->sess_start_ts );
$this->lastseen_ts = mysql2timestamp( $row->sess_lastseen_ts );
$this->is_validated = true;
$this->sess_device = $row->sess_device;
$Debuglog->add( 'Session: Session user_ID: '.var_export( $this->user_ID, true ), 'request' );
if( empty( $row->sess_data ) )
{
$Debuglog->add( 'Session: No session data available.', 'request' );
$this->_data = array();
}
else
{ // Some session data has been previsouly stored:
// Unserialize session data (using an own callback that should provide class definitions):
$old_callback = @ini_set( 'unserialize_callback_func', 'session_unserialize_callback' );
if( $old_callback === false || is_null( $old_callback ) /* disabled, reported with PHP 5.2.5 */ )
{ // NULL if ini_set has been disabled for security reasons
// Brutally load all classes that we might need:
session_unserialize_load_all_classes();
}
// TODO: dh> This can fail, if there are special chars in sess_data:
// It will be encoded in $evo_charset _after_ mysqli::set_charset but
// get retrieved here, _before_ any mysqli::set_charset (if $db_config['connection_charset'] is not set (default))!
$this->_data = @unserialize( $row->sess_data );
if( $old_callback !== false )
{ // Restore the old callback if we changed it:
@ini_set( 'unserialize_callback_func', $old_callback );
}
if( ! is_array( $this->_data ) )
{
$Debuglog->add( 'Session: Session data corrupted!<br />
connection_charset: '.var_export( $DB->get_connection_charset(), true ).'<br />
Serialized data was: --['.var_export( $row->sess_data, true ).']--', array('session','error') );
$this->_data = array();
}
else
{
$Debuglog->add( 'Session: Session data loaded.', 'request' );
// Load a Messages object from session data, if available:
if( ( $sess_Messages = $this->get('Messages') ) && $sess_Messages instanceof Messages )
{
// dh> TODO: "old" messages should rather get prepended to any existing ones from the current request, rather than appended
$Messages->add_messages( $sess_Messages );
$Messages->affixed = $sess_Messages->affixed;
$Debuglog->add( 'Session: Added Messages from session data.', 'request' );
$this->delete( 'Messages' );
}
}
}
}
if( $this->ID )
{ // there was a valid session before
if( $this->lastseen_ts < $localtimenow - 60 )
{ // lastseen timestamp is older then a minute, it needs to be updated at page exit
$this->session_needs_save( true );
}
}
elseif( $session_ID === NULL )
{ // Create a new session! Only for session from cookie:
// Do NOT create new session on request by ID!
$this->key = generate_random_key( 32 );
// Detect user device
global $user_devices;
$this->sess_device = '';
if( ! empty( $_SERVER['HTTP_USER_AGENT'] ) )
{
foreach( $user_devices as $device_name => $device_regexp )
{
if( preg_match( '~'.$device_regexp.'~i', $_SERVER['HTTP_USER_AGENT'] ) )
{
$this->sess_device = $device_name;
break;
}
}
}
// We need to INSERT now because we need an ID now! (for the cookie)
$DB->query( 'INSERT INTO T_sessions( sess_key, sess_start_ts, sess_lastseen_ts, sess_ipaddress, sess_device )
VALUES (
'.$DB->quote( $this->key ).',
FROM_UNIXTIME('.$localtimenow.'),
FROM_UNIXTIME('.$localtimenow.'),
'.$DB->quote( $Hit->IP ).',
'.$DB->quote( $this->sess_device ).'
)' );
$this->ID = $DB->insert_id;
// Set a cookie valid for ~ 10 years:
evo_setcookie( $cookie_session, $this->ID.'_'.$this->key, time()+315360000, '', '', false, true );
$Debuglog->add( 'Session: ID (generated): '.$this->ID, 'request' );
$Debuglog->add( 'Session: Cookie sent.', 'request' );
}
}
/**
* Get this class db table config params
*
* @return array
*/
static function get_class_db_config()
{
static $session_db_config;
if( !isset( $session_db_config ) )
{
$session_db_config = array(
'dbtablename' => 'T_sessions',
'dbprefix' => 'sess_',
'dbIDname' => 'sess_ID',
);
}
return $session_db_config;
}
/**
* Delete sessions from database based on where condition or by object ids
*
* @param string where condition
* @param array object ids
* @param array additional params if required
* @return mixed # of rows affected or false if error
*/
static function db_delete_where( $sql_where, $object_ids = NULL, $params = NULL )
{
global $DB;
$DB->begin();
if( ! empty( $sql_where ) )
{
$object_ids = $DB->get_col( 'SELECT sess_ID FROM T_sessions WHERE '.$sql_where );
}
if( ! $object_ids )
{ // There is no session to delete
$DB->commit();
return;
}
$session_ids_to_delete = implode( ', ', $object_ids );
$result = $DB->query( 'DELETE FROM T_hitlog WHERE hit_sess_ID IN ( '.$session_ids_to_delete.' )' );
$result = ( $result !== false ) ? $DB->query( 'DELETE FROM T_sessions WHERE sess_ID IN ( '.$session_ids_to_delete.')' ) : $result;
( $result !== false ) ? $DB->commit() : $DB->rollback();
return $result;
}
function session_needs_save( $session_needs_save )
{
// pre_dump( 'SETTING session needs save to', $session_needs_save );
$this->_session_needs_save = $session_needs_save;
}
/**
* Attach a User object to the session.
*
* @param User The user to attach
*/
function set_User( $User )
{
return $this->set_user_ID( $User->ID );
}
/**
* Attach a user ID to the session.
*
* NOTE: ID gets saved to DB on shutdown. This may be a "problem" when querying T_sessions for sess_user_ID.
*
* @param integer The ID of the user to attach
*/
function set_user_ID( $user_ID )
{
if( $user_ID != $this->user_ID )
{
global $Settings, $UserSettings, $DB;
$multiple_sessions = $Settings->get( 'multiple_sessions' );
if( $multiple_sessions != 'always' && ( $multiple_sessions == 'never' || !$UserSettings->get('login_multiple_sessions', $user_ID) ) )
{ // The user does not want/is not allowed to have multiple sessions open at the same time:
// Invalidate previous sessions:
global $Debuglog;
$Debuglog->add( 'Session: Invalidating all previous user sessions, because login_multiple_sessions=0', 'request' );
$DB->query( '
UPDATE T_sessions
SET sess_key = NULL
WHERE sess_user_ID = '.$DB->quote( $user_ID ).'
AND sess_ID != '.$this->ID );
}
$this->user_ID = $user_ID;
$this->session_needs_save( true );
}
}
/**
* Logout the user, by invalidating the session key and unsetting {@link $user_ID}.
*
* We want to keep the user in the session log, but we're unsetting {@link $user_ID}, which refers
* to the current session.
*
* Because the session key is invalid/broken, on the next request a new session will be started.
*
* NOTE: we MIGHT want to link subsequent sessions together if we want to keep track...
*/
function logout()
{
global $Debuglog, $cookie_session, $Settings;
if( $Settings->get( 'http_auth_accept' ) && isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) )
{ // Don't log out when user is logged in by HTTP basic authentication because it is possible only after browser closing:
// (to avoid infinite redirection after logout to $baseurl)
return;
}
// Invalidate the session key (no one will be able to use this session again)
$this->key = NULL;
$this->_data = array(); // We don't need to keep old data
$this->session_needs_save( true );
$this->dbsave();
$this->user_ID = NULL; // Unset user_ID after invalidating/saving the session above, to keep the user info attached to the old session.
// clean up the session cookie:
evo_setcookie( $cookie_session, '', 200000000, '', '', false, true );
}
/**
* Check if session has a user attached.
*
* @return boolean
*/
function has_User()
{
return !empty( $this->user_ID );
}
/**
* Get the attached User.
*
* @return false|User
*/
function & get_User()
{
if( ! empty( $this->user_ID ) )
{
$UserCache = & get_UserCache();
return $UserCache->get_by_ID( $this->user_ID );
}
$r = false;
return $r;
}
/**
* Get a data value for the session. This checks for the data to be expired and unsets it then.
*
* @param string Name of the data's key.
* @param mixed Default value to use if key is not set or has expired. (since 1.10.0)
* @return mixed The value, if set; otherwise $default
*/
function get( $param, $default = NULL )
{
global $Debuglog, $localtimenow;
if( isset( $this->_data[$param] ) )
{
if( array_key_exists( 1, $this->_data[$param] ) // can be NULL!
&& ( is_null( $this->_data[$param][0] ) || $this->_data[$param][0] > $localtimenow ) ) // check for expired data
{
return $this->_data[$param][1];
}
else
{ // expired or old format (without 'value' key)
unset( $this->_data[$param] );
$this->session_needs_save( true );
$Debuglog->add( 'Session: Session data['.$param.'] expired.', 'request' );
}
}
return $default;
}
/**
* Set a data value for the session.
*
* Updated values get saved to the DB automatically on shutdown, in {@link shutdown()}.
*
* @param string Name of the data's key.
* @param mixed The value
* @param integer Time in seconds for data to expire (0 to disable).
*/
function set( $param, $value, $expire = 0 )
{
global $Debuglog, $localtimenow;
if( ! isset( $this->_data[$param] )
|| ! is_array( $this->_data[$param] ) // deprecated: check to transform 1.6 session data to 1.7
|| $this->_data[$param][1] != $value
|| $expire != 0 )
{ // There is something to update:
if( isset( $this->_data[ $param ] ) )
{ // Log updated session var:
if( isset( $this->_data[ $param ][1] ) )
{
if( is_scalar( $this->_data[ $param ][1] ) )
{ // Display value for scalar var:
$session_old_value = '<code class="debug_code_inline">'.$this->_data[ $param ][1].'</code>';
}
elseif( is_array( $this->_data[ $param ][1] ) )
{ // Display only type for array var:
$session_old_value = 'Array( '.implode( ', ', array_keys( $this->_data[ $param ][1] ) ).' )';
}
elseif( is_object( $this->_data[ $param ][1] ) )
{ // Display class name for object var:
$session_old_value = 'Object: '.get_class( $this->_data[ $param ][1] );
}
else
{ // Display var type for others:
$session_old_value = gettype( $this->_data[ $param ][1] );
}
}
else
{ // No old value:
$session_old_value = '';
}
$action_type = 'updated (old value: '.$session_old_value.')';
}
else
{ // Log created session var:
$action_type = 'created';
}
$this->_data[$param] = array( ( $expire ? ( $localtimenow + $expire ) : NULL ), $value );
if( $param == 'Messages' )
{ // also set boolean to not call CachePageContent plugin event on next request:
$this->set( 'core.no_CachePageContent', 1 );
}
$Debuglog->add( 'Session: Session data['.$param.'] '.$action_type.'. Expire in: '.( $expire ? $expire.'s' : 'never' ).'.', 'request' );
$this->session_needs_save( true );
}
}
/**
* Delete a value from the session data.
*
* @param string Name of the data's key.
*/
function delete( $param )
{
global $Debuglog;
if( isset( $this->_data[$param] ) )
{
unset( $this->_data[$param] );
$Debuglog->add( 'Session: Session data['.$param.'] deleted!', 'request' );
$this->session_needs_save( true );
}
}
/**
* Updates session data in database.
*
* NOTE: Debuglog additions will may not be displayed since the debuglog may alreayd have been displayed (shutdown function)
*/
function dbsave()
{
global $DB, $Debuglog, $Hit, $localtimenow;
if( ! $this->_session_needs_save )
{ // There have been no changes since the last save.
$Debuglog->add( 'Session: Session is up to date and does not need to be saved.', 'request' );
return false;
}
$sess_data = empty( $this->_data ) ? NULL : serialize( $this->_data );
// Note: The key actually only needs to be updated on a logout.
// Note: we increase the hitcoutn every time. That assumes that there will be no 2 calls for a single hit.
// Anyway it is not a big problem if this number is approximate.
$sql = "UPDATE T_sessions SET
sess_lastseen_ts = FROM_UNIXTIME(".$localtimenow."),
sess_data = ".$DB->quote( $sess_data ).",
sess_ipaddress = '".$Hit->IP."',
sess_key = ".$DB->quote( $this->key );
if( ! is_null( $this->user_ID ) )
{ // We do NOT erase existing IDs at logout. We only want to set IDs at login:
$sql .= ", sess_user_ID = ".$this->user_ID;
}
$sql .= " WHERE sess_ID = ".$this->ID;
$DB->query( $sql, 'Session::dbsave()' );
$Debuglog->add( 'Session: Session data saved!', 'request' );
$this->session_needs_save( false );
}
/**
* Reload session data.
*
* This is needed if the running process waits for a child process to write data
* into the Session, e.g. the captcha plugin in test mode waiting for the Debuglog
* output from the process that created the image (included through an IMG tag).
*/
function reload_data()
{
global $Debuglog, $DB;
if( empty( $this->ID ) )
{
return false;
}
$sess_data = $DB->get_var( '
SELECT SQL_NO_CACHE sess_data FROM T_sessions
WHERE sess_ID = '.$this->ID );
$sess_data = @unserialize( $sess_data );
if( $sess_data === false )
{
$this->_data = array();
}
else
{
$this->_data = $sess_data;
}
$Debuglog->add( 'Session: Reloaded session data.' );
}
/**
* Create a crumb that will be saved into the Session and returned to the caller for inclusion in Form or action url.
*
* For any action, a new crumb is generated every hour and the previous one is saved. (2 hours are valid)
*
* @param string crumb name
* @return string crumb value
*/
function create_crumb( $crumb_name )
{
global $servertimenow, $crumb_expires, $Debuglog;
// Retrieve latest saved crumb:
$crumb_recalled = $this->get( 'crumb_latest_'.$crumb_name, '-0' );
list( $crumb_value, $crumb_time ) = explode( '-', $crumb_recalled );
if( $servertimenow - $crumb_time > ( $crumb_expires / 2 ) )
{ // The crumb we already had is older than 1 hour...
// We'll need to generate a new value:
$crumb_value = '';
if( $servertimenow - $crumb_time < ( $crumb_expires - 200 ) ) // Leave some margin here to make sure we do no overwrite a newer 1-2 hr crumb
{ // Not too old either, save as previous crumb:
$Debuglog->add( 'Session: Store previous value <code class="debug_code_inline">'.$crumb_value.'-'.$servertimenow.'</code> for crumb ['.$crumb_name.'].', 'request' );
$this->set( 'crumb_prev_'.$crumb_name, $crumb_recalled );
}
}
if( empty( $crumb_value ) )
{ // We need to generate a new crumb:
$crumb_value = generate_random_key( 32 );
$Debuglog->add( 'Session: Add new crumb ['.$crumb_name.'] = <code class="debug_code_inline">'.$crumb_value.'-'.$servertimenow.'</code> (Store the latest value).', 'request' );
// Save crumb into session so we can later compare it to what get got back from the user request:
$this->set( 'crumb_latest_'.$crumb_name, $crumb_value.'-'.$servertimenow );
}
return $crumb_value;
}
/**
* Assert that we received a valid crumb for the object we want to act on.
*
* This will DIE if we have not received a valid crumb.
*
* The received crumb must match a crumb we previously saved less than 2 hours ago.
*
* @param string crumb name
* @param boolean true if the script should die on error
* @param array Additional parameters
*/
function assert_received_crumb( $crumb_name, $die = true, $params = array() )
{
global $servertimenow, $crumb_expires, $debug;
$params = array_merge( array(
'msg_format' => 'html',
'msg_no_crumb' => 'Missing crumb ['.$crumb_name.'] -- It looks like this request is not legit.',
'msg_incorrect_crumb' => T_('Incorrect crumb received!'),
'msg_expired_crumb' => T_('Expired crumb received!'),
'error_code' => '400 Bad Crumb Request',
), $params );
if( ! $crumb_received = param( 'crumb_'.$crumb_name, 'string', NULL ) )
{ // We did not receive a crumb!
if( $die )
{
// Add message to AJAX Log:
ajax_log_add( $params['msg_no_crumb'].' ['.$crumb_name.']', 'error' );
// Die with error message:
bad_request_die( $params['msg_no_crumb'], $params['error_code'], $params['msg_format'] );
}
return false;
}
// Retrieve latest and previous crumbs:
list( $crumb_latest, $latest_crumb_time ) = explode( '-', $this->get( 'crumb_latest_'.$crumb_name, '-0' ) );
list( $crumb_previous, $prev_crumb_time ) = explode( '-', $this->get( 'crumb_prev_'.$crumb_name, '-0' ) );
// Validate the latest and previous crumbs:
$crumb_latest_is_valid = ( $crumb_received == $crumb_latest );
$crumb_previous_is_valid = ( $crumb_received == $crumb_previous );
if( $crumb_latest_is_valid || $crumb_previous_is_valid )
{ // At least one crumb is valid, check for expired:
if( $crumb_latest_is_valid && $servertimenow - $latest_crumb_time <= $crumb_expires )
{ // The latest crumb is valid and not expired:
return true;
}
if( $crumb_previous_is_valid && $servertimenow - $prev_crumb_time <= $crumb_expires )
{ // The previous crumb is valid and not expired:
return true;
}
// The latest and previous crumbs are expired:
$crumb_info_message = $params['msg_expired_crumb'];
$crumb_latest_status = ( $crumb_latest_is_valid ? 'EXPIRED' : 'Invalid' );
$crumb_previous_status = ( $crumb_previous_is_valid ? 'EXPIRED' : 'Invalid' );
}
else
{ // The latest and previous crumbs are invalid:
$crumb_info_message = $params['msg_incorrect_crumb'];
$crumb_latest_status = 'Invalid';
$crumb_previous_status = 'Invalid';
}
if( ! $die )
{ // Return false result when we don't need die action on error:
return false;
}
// Attempt to output an error header (will not work if the output buffer has already flushed once):
// This should help preventing indexing robots from indexing the error :P
if( ! headers_sent() )
{
load_funcs( '_core/_template.funcs.php' );
headers_content_mightcache( 'text/html', 0, '#', false ); // Do NOT cache error messages! (Users would not see they fixed them)
header_http_response( $params['error_code'] );
}
$crumb_info_full_message = $crumb_info_message.' '.sprintf( T_('Have you waited more than %d minutes before submitting your request?'), floor( $crumb_expires / 60 ) );
// Info for debug log:
$received_crumb_info = 'Received crumb: '.$crumb_received;
$latest_crumb_info = 'Latest saved crumb: '.( empty( $crumb_latest ) && empty( $latest_crumb_time )
? 'Not saved yet' // The latest crumb was not saved in Sessions yet.
: $crumb_latest.' (Created at: '.date( 'Y-m-d H:i:s', $latest_crumb_time ).' - '.$crumb_latest_status.')' );
$previous_crumb_info = 'Previous saved crumb: '.( empty( $crumb_previous ) && empty( $prev_crumb_time )
? 'Not saved yet' // Previous crumb was not saved in Sessions yet.
: $crumb_previous.' (Created at: '.date( 'Y-m-d H:i:s', $prev_crumb_time ).' - '.$crumb_previous_status.')' );
$session_info = 'Session ID: '.$this->ID;
// Add messages to AJAX Log:
ajax_log_add( $crumb_info_full_message.' (Config var: <code class="debug_code_inline">$crumb_expires</code> = '.$crumb_expires.' seconds)', 'error' );
ajax_log_add( $received_crumb_info, 'error' );
ajax_log_add( $latest_crumb_info, 'error' );
ajax_log_add( $previous_crumb_info, 'error' );
ajax_log_add( $session_info, 'error' );
if( $params['msg_format'] == 'text' )
{ // Display error message in TEXT format:
echo $crumb_info_full_message;
// Display AJAX Log if it was initialized:
ajax_log_display();
die();
}
// else display error message in HTML format:
// ERROR MESSAGE, with form/button to bypass and enough warning hopefully.
// TODO: dh> please review carefully!
global $io_charset;
// erhsatingin > Most browsers will output this is in the <head> section. Either this or possibility of multiple <head> declarations.
// echo '<head>';
echo '<meta http-equiv="Content-Type" content="text/html; charset='.$io_charset.'" />';
// echo '</head>';
echo '<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">';
echo '<h3 style="color:#f00;">'.$crumb_info_message.' ['.$crumb_name.']</h3>';
echo '<p>'.T_('Your request was stopped for security reasons.').'</p>';
echo '<p>'.$crumb_info_full_message.'</p>';
echo '<p>'.T_('Please go back to the previous page and refresh it before submitting the form again.').'</p>';
echo '</div>';
if( $debug > 0 )
{ // Display additional info when debug is enabled:
echo '<div>';
echo '<p>'.$received_crumb_info.'</p>';
echo '<p>'.$latest_crumb_info.'</p>';
echo '<p>'.$previous_crumb_info.'</p>';
echo '<p>'.$session_info.'</p>';
echo '</div>';
}
echo '<div>';
echo '<p class="warning">'.T_('Alternatively, you can try to resubmit your request with a refreshed crumb:').'</p>';
$Form = new Form( '', 'evo_session_crumb_resend', $_SERVER['REQUEST_METHOD'] );
$Form->begin_form( 'inline' );
$Form->add_crumb( $crumb_name );
$Form->hiddens_by_key( $_REQUEST );
$Form->button( array( 'submit', '', T_('Resubmit now!'), 'ActionButton' ) );
$Form->end_form();
echo '</div>';
// Display AJAX Log if it was initialized:
ajax_log_display();
die();
}
/**
* Was this session created from a mobile device
*/
function is_mobile_session()
{
global $mobile_user_devices;
return array_key_exists( $this->sess_device, $mobile_user_devices );
}
/**
* Was this session created from a mobile device
*/
function is_tablet_session()
{
global $tablet_user_devices;
return array_key_exists( $this->sess_device, $tablet_user_devices );
}
/**
* Was this session specially switched to alternative skin
*/
function is_alt_session()
{
global $Blog, $Hit;
if( $this->get( 'using_alt_skin' ) )
{ // Do additional check to know if we can continue to use Alt skin:
if( isset( $Blog ) &&
strpos( $Hit->get_referer(), $Blog->get( 'url' ) ) === 0 )
{ // We can continue to use Alt skin because referer URL is started with collection base URL:
return true;
}
else
{ // Don't continue to use Alt skin because referer is not started with collection base URL:
$this->delete( 'using_alt_skin' );
}
}
if( isset( $Hit ) &&
isset( $Blog ) &&
$Blog->get_setting( 'display_alt_skin_referer' ) &&
strpos( $Hit->get_referer(), $Blog->get_setting( 'display_alt_skin_referer_url' ) ) !== false )
{ // Current referer URL contains string from collection setting:
$this->set( 'using_alt_skin', true );
return true;
}
// No condition is found to display Alt skin:
return false;
}
/**
* Was this session created from a desktop device
*/
function is_desktop_session()
{
global $pc_user_devices;
return array_key_exists( $this->sess_device, $pc_user_devices );
}
/**
* Get first hit params of this session
*
* @return object Params, array, keys are names of T_hitlog fields: hit_ID, hit_sess_ID, hit_uri, hit_referer, hit_referer_dom_ID and etc.
*/
function get_first_hit_params()
{
if( ! isset( $this->first_hit_params ) )
{ // Get the params from DB only first time and then cache them:
global $DB;
$SQL = new SQL( 'Get first hit params of the session #'.$this->ID );
$SQL->SELECT( '*' );
$SQL->FROM( 'T_hitlog' );
$SQL->WHERE( 'hit_sess_ID = '.$DB->quote( $this->ID ) );
$SQL->ORDER_BY( 'hit_ID ASC' );
$SQL->LIMIT( '1' );
$this->first_hit_params = $DB->get_row( $SQL );
}
return $this->first_hit_params;
}
}
/**
* This gets used as a {@link unserialize()} callback function, which is
* responsible for loading the requested class.
*
* IMPORTANT: when modifying this, modify the following also:
* @see session_unserialize_load_all_classes()
*
* @todo Once we require PHP5, we should think about using this as __autoload function.
*
* @return boolean True, if the required class could be loaded; false, if not
*/
function session_unserialize_callback( $classname )
{
switch( strtolower( $classname ) )
{
case 'blog':
load_class( 'collections/model/_blog.class.php', 'Blog' );
return true;
case 'collectionsettings':
load_class( 'collections/model/_collsettings.class.php', 'CollectionSettings' );
return true;
case 'comment':
load_class( 'comments/model/_comment.class.php', 'Comment' );
return true;
case 'item':
load_class( 'items/model/_item.class.php', 'Item' );
return true;
case 'itemsettings':
load_class( 'items/model/_itemsettings.class.php', 'ItemSettings' );
return true;
case 'group':
load_class( 'users/model/_group.class.php', 'Group' );
return true;
case 'user':
load_class( 'users/model/_user.class.php', 'User' );
return true;
}
return false;
}
/**
* When session_unserialize_callback() cannot be registered to do some smart loading,
* then we fall back to this function and load everything with brute force...
*
* IMPORTANT: when modifying this, modify the following also:
* @see session_unserialize_callback()
*/
function session_unserialize_load_all_classes()
{
load_class( 'collections/model/_blog.class.php', 'Blog' );
load_class( 'collections/model/_collsettings.class.php', 'CollectionSettings' );
load_class( 'comments/model/_comment.class.php', 'Comment' );
load_class( 'items/model/_item.class.php', 'Item' );
load_class( 'items/model/_itemsettings.class.php', 'ItemSettings' );
load_class( 'users/model/_group.class.php', 'Group' );
load_class( 'users/model/_user.class.php', 'User' );
}
?>