yf_session classCore session handler.
| File | plugins/session/classes/yf_session.class.php |
|---|---|
| Use | _class('session') |
A site sets them in its config with $PROJECT_CONF['session']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$COOKIE_HTTPONLYbool |
true |
|
$COOKIE_PATHstring |
'/' |
|
$COOKIE_SAMESITEstring |
'None' |
SameSite of the session cookie; 'Lax' recommended, 'None' kept as the default until 4.0 |
$COOKIE_SECUREbool |
false |
|
$CUSTOM_HANDLERstring |
'' |
Custom handler name |
$DESTROY_EXPIREDstring |
false |
|
$DOMAINstring |
'' |
|
$LIFE_TIMEint |
18000 |
Session life time (in seconds) |
$NAMEstring |
'' |
Current session name |
$OFFbool |
false |
Disable sessions or not |
$REFERER_CHECKstring |
'' |
|
$SAVE_DIRstring |
'' |
Custom session save dir (leave ampty to skip), example: 'session_data/' |
$USE_STRICT_MODEbool |
false |
SECURITY: session.use_strict_mode, PHP refuses session ids it did not create (stops session fixation); default false keeps the previous ini value, 4.0 flips |
$USE_UNIQUE_NAMEstring |
true |
Custom session name |
$_driverbool |
null |
@conf_skip |
$_startedbool |
false |
@conf_skip |
public function start()
public function start()
{
$main = main();
if ( ! empty($this->_started) || $main->is_console() || conf('SESSION_OFF') || $this->OFF) {
return false;
}
// Set custom session name
if ($this->USE_UNIQUE_NAME) {
$force_name_path = conf('_SESSION_FORCE_NAME_PATH');
$_name_path = isset($force_name_path) ? $force_name_path : (MAIN_TYPE_ADMIN ? ADMIN_SITE_PATH : SITE_PATH);
$_name_path = str_replace('\\', '/', OS_WINDOWS ? strtolower($_name_path) : $_name_path);
$this->NAME = 'sid_' . (int) (abs(crc32($_name_path)));
session_name($this->NAME);
} else {
$this->NAME = ini_get('session.name'); // Usually PHPSESSID
}
$main->SESSION_NAME = $this->NAME;
if (session_id() !== '') { // (session_status() == PHP_SESSION_ACTIVE) => PHP 5.4+ only
return true;
}
if (ini_get('session.use_trans_sid')) {
@ini_set('session.use_trans_sid', 0); // We need @ here to avoid error when session already started
}
ini_set('url_rewriter.tags', '');
if ( ! empty($this->LIFE_TIME)) {
ini_set('session.cookie_lifetime', $this->LIFE_TIME);
ini_set('session.gc_probability', 0);
ini_set('session.gc_maxlifetime', $this->LIFE_TIME);
}
ini_set('session.use_cookies', 1);
if ( ! ini_get('session.use_only_cookies')) {
ini_set('session.use_only_cookies', 1);
}
if ($this->COOKIE_PATH) {
ini_set('session.cookie_path', $this->COOKIE_PATH);
}
if ($this->DOMAIN) {
ini_set('session.cookie_domain', $this->DOMAIN);
}
if ($this->COOKIE_SECURE) {
ini_set('session.cookie_secure', 1);
}
if ($this->COOKIE_HTTPONLY) {
ini_set('session.cookie_httponly', 1);
}
if ($this->USE_STRICT_MODE) {
ini_set('session.use_strict_mode', 1);
}
if ($this->REFERER_CHECK) { // WEB_PATH
ini_set('session.referer_check', $this->REFERER_CHECK);
}
conf('COOKIES_ENABLED', ($_COOKIE[$this->NAME] ?? null) !== null ? 1 : 0);
// Check if we have valid session name
if (($_COOKIE[$this->NAME] ?? null) !== null) {
$_test_result = preg_replace('/[^a-z0-9]/i', '', $_COOKIE[$this->NAME]);
if ($_test_result !== ($_COOKIE[$this->NAME] ?? null)) {
$_COOKIE[$this->NAME] = abs(crc32(microtime(true)));
session_id(($_COOKIE[$this->NAME] ?? null));
}
}
if ( ! empty($this->SAVE_DIR)) {
$s_path = STORAGE_PATH . $this->SAVE_DIR;
if ( ! file_exists($s_path)) {
mkdir($s_path, 0755, true);
}
session_save_path($s_path);
}
if ($this->CUSTOM_HANDLER && $this->_driver_setup($this->CUSTOM_HANDLER)) {
$main->session = $this->_driver;
}
// cross-site: 'None' needs Secure, so it only applies over HTTPS; 'Lax'/'Strict' work on both
$samesite = $this->_samesite($this->COOKIE_SAMESITE);
if (is_https()) {
session_set_cookie_params(['SameSite' => $samesite, 'Secure' => true]);
} elseif ($samesite !== 'None') {
session_set_cookie_params(['SameSite' => $samesite]);
}
session_start();
// Instruct bots to totally ignore current page
if (DEBUG_MODE || MAIN_TYPE_ADMIN || conf('ROBOTS_NO_INDEX')) {
header('X-Robots-Tag: noindex,nofollow,noarchive,nosnippet');
}
$now = time();
$last_update = ($_SESSION['last_update'] ?? null);
if ($last_update) {
$diff = $now - $last_update;
$percent = $diff / $this->LIFE_TIME * 100;
// Session expired
if ($percent > 100 && $this->DESTROY_EXPIRED) {
session_regenerate_id($destroy = true);
// Session need to be regenerated
} elseif ($percent > 10) {
session_regenerate_id();
$_SESSION['last_update'] = $now;
}
} else {
$_SESSION['last_update'] = $now;
}
$_SESSION['sys_main_type'] = MAIN_TYPE;
@$_SESSION['sys_requests_count']++;
$_SESSION['sys_user_ip'] = common()->get_ip();
$_SESSION['sys_user_geo_country'] = $_SERVER['GEOIP_COUNTRY_CODE'] ?? '';
$_SESSION['sys_user_current_url'] = (is_https() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$main->_session_init_complete = true;
$this->_started = true;
return $this->_started;
}
public function stop()
public function stop()
{
session_write_close();
return true;
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
public function _driver_setup($driver_name)
public function _driver_setup($driver_name)
{
$session_class_name = 'session_driver_' . $driver_name;
$session_loaded_class_name = main()->load_class_file($session_class_name, 'classes/session/');
if (empty($session_loaded_class_name)) {
return false;
}
$this->_driver = new $session_loaded_class_name();
main()->set_module_conf($session_class_name, $this->_driver);
// PHP 8.4 deprecates the callable form of session_set_save_handler(); the adapter keeps every driver (including project ones) working unchanged
require_once __DIR__ . '/session/yf_session_handler_adapter.class.php';
session_set_save_handler(new yf_session_handler_adapter($this->_driver), true);
return $this->_driver;
}
public function _init()
public function _init()
{
$main = main();
// Import session settings from main class, but prefixed with "SESSION_"
$prefix = 'SESSION_';
$plen = strlen($prefix);
foreach (get_object_vars($main) as $k => $v) {
if (substr($k, 0, $plen) !== $prefix) {
continue;
}
$name = substr($k, $plen);
$this->$name = $v;
}
}
public function _samesite($value)
'None', 'Lax' or 'Strict' in any case; anything else falls back to 'Lax' rather than sending a bad attribute.
public function _samesite($value)
{
$value = ucfirst(strtolower(trim((string) $value)));
return in_array($value, ['None', 'Lax', 'Strict'], true) ? $value : 'Lax';
}