Your IP : 18.223.171.83
<?php
/**
* The plugin vary class to manage X-LiteSpeed-Vary
*
* @since 1.1.3
*/
namespace LiteSpeed;
defined('WPINC') || exit();
class Vary extends Root
{
const X_HEADER = 'X-LiteSpeed-Vary';
private static $_vary_name = '_lscache_vary'; // this default vary cookie is used for logged in status check
private static $_can_change_vary = false; // Currently only AJAX used this
/**
* Adds the actions used for setting up cookies on log in/out.
*
* Also checks if the database matches the rewrite rule.
*
* @since 1.0.4
*/
public function init()
{
$this->_update_vary_name();
}
/**
* Update the default vary name if changed
*
* @since 4.0
*/
private function _update_vary_name()
{
$db_cookie = $this->conf(Base::O_CACHE_LOGIN_COOKIE); // [3.0] todo: check if works in network's sites
// If no vary set in rewrite rule
if (!isset($_SERVER['LSCACHE_VARY_COOKIE'])) {
if ($db_cookie) {
// Display cookie error msg to admin
if (is_multisite() ? is_network_admin() : is_admin()) {
Admin_Display::show_error_cookie();
}
Control::set_nocache('vary cookie setting error');
return;
}
return;
}
// If db setting does not exist, skip checking db value
if (!$db_cookie) {
return;
}
// beyond this point, need to make sure db vary setting is in $_SERVER env.
$vary_arr = explode(',', $_SERVER['LSCACHE_VARY_COOKIE']);
if (in_array($db_cookie, $vary_arr)) {
self::$_vary_name = $db_cookie;
return;
}
if (is_multisite() ? is_network_admin() : is_admin()) {
Admin_Display::show_error_cookie();
}
Control::set_nocache('vary cookie setting lost error');
}
/**
* Hooks after user init
*
* @since 4.0
*/
public function after_user_init()
{
// logged in user
if (Router::is_logged_in()) {
// If not esi, check cache logged-in user setting
if (!$this->cls('Router')->esi_enabled()) {
// If cache logged-in, then init cacheable to private
if ($this->conf(Base::O_CACHE_PRIV)) {
add_action('wp_logout', __NAMESPACE__ . '\Purge::purge_on_logout');
$this->cls('Control')->init_cacheable();
Control::set_private('logged in user');
}
// No cache for logged-in user
else {
Control::set_nocache('logged in user');
}
}
// ESI is on, can be public cache
else {
// Need to make sure vary is using group id
$this->cls('Control')->init_cacheable();
}
// register logout hook to clear login status
add_action('clear_auth_cookie', array($this, 'remove_logged_in'));
} else {
// Only after vary init, can detect if is Guest mode or not
$this->_maybe_guest_mode();
// Set vary cookie for logging in user, otherwise the user will hit public with vary=0 (guest version)
add_action('set_logged_in_cookie', array($this, 'add_logged_in'), 10, 4);
add_action('wp_login', __NAMESPACE__ . '\Purge::purge_on_logout');
$this->cls('Control')->init_cacheable();
// Check `login page` cacheable setting because they don't go through main WP logic
add_action('login_init', array($this->cls('Tag'), 'check_login_cacheable'), 5);
if (!empty($_GET['litespeed_guest'])) {
add_action('wp_loaded', array($this, 'update_guest_vary'), 20);
}
}
// Add comment list ESI
add_filter('comments_array', array($this, 'check_commenter'));
// Set vary cookie for commenter.
add_action('set_comment_cookies', array($this, 'append_commenter'));
/**
* Don't change for REST call because they don't carry on user info usually
* @since 1.6.7
*/
add_action('rest_api_init', function () {
// this hook is fired in `init` hook
Debug2::debug('[Vary] Rest API init disabled vary change');
add_filter('litespeed_can_change_vary', '__return_false');
});
}
/**
* Check if is Guest mode or not
*
* @since 4.0
*/
private function _maybe_guest_mode()
{
if (defined('LITESPEED_GUEST')) {
Debug2::debug('[Vary] 👒👒 Guest mode ' . (LITESPEED_GUEST ? 'predefined' : 'turned off'));
return;
}
if (!$this->conf(Base::O_GUEST)) {
return;
}
// If vary is set, then not a guest
if (self::has_vary()) {
return;
}
// If has admin QS, then no guest
if (!empty($_GET[Router::ACTION])) {
return;
}
if (defined('DOING_AJAX')) {
return;
}
if (defined('DOING_CRON')) {
return;
}
// If is the request to update vary, then no guest
// Don't need anymore as it is always ajax call
// Still keep it in case some WP blocked the lightweigh guest vary update script, WP can still update the vary
if (!empty($_GET['litespeed_guest'])) {
return;
}
/* @ref https://wordpress.org/support/topic/checkout-add-to-cart-executed-twice/ */
if (!empty($_GET['litespeed_guest_off'])) {
return;
}
Debug2::debug('[Vary] 👒👒 Guest mode');
!defined('LITESPEED_GUEST') && define('LITESPEED_GUEST', true);
if ($this->conf(Base::O_GUEST_OPTM)) {
!defined('LITESPEED_GUEST_OPTM') && define('LITESPEED_GUEST_OPTM', true);
}
}
/**
* Update Guest vary
*
* @since 4.0
* @deprecated 4.1 Use independent lightweight guest.vary.php as a replacement
*/
public function update_guest_vary()
{
// This process must not be cached
!defined('LSCACHE_NO_CACHE') && define('LSCACHE_NO_CACHE', true);
$_guest = new Lib\Guest();
if ($_guest->always_guest() || self::has_vary()) {
// If contains vary already, don't reload to avoid infinite loop when parent page having browser cache
!defined('LITESPEED_GUEST') && define('LITESPEED_GUEST', true); // Reuse this const to bypass set vary in vary finalize
Debug2::debug('[Vary] 🤠🤠Guest');
echo '[]';
exit();
}
Debug2::debug('[Vary] Will update guest vary in finalize');
// return json
echo json_encode(array('reload' => 'yes'));
exit();
}
/**
* Hooked to the comments_array filter.
*
* Check if the user accessing the page has the commenter cookie.
*
* If the user does not want to cache commenters, just check if user is commenter.
* Otherwise if the vary cookie is set, unset it. This is so that when the page is cached, the page will appear as if the user was a normal user.
* Normal user is defined as not a logged in user and not a commenter.
*
* @since 1.0.4
* @access public
* @global type $post
* @param array $comments The current comments to output
* @return array The comments to output.
*/
public function check_commenter($comments)
{
/**
* Hook to bypass pending comment check for comment related plugins compatibility
* @since 2.9.5
*/
if (apply_filters('litespeed_vary_check_commenter_pending', true)) {
$pending = false;
foreach ($comments as $comment) {
if (!$comment->comment_approved) {
// current user has pending comment
$pending = true;
break;
}
}
// No pending comments, don't need to add private cache
if (!$pending) {
Debug2::debug('[Vary] No pending comment');
$this->remove_commenter();
// Remove commenter prefilled info if exists, for public cache
foreach ($_COOKIE as $cookie_name => $cookie_value) {
if (strlen($cookie_name) >= 15 && strpos($cookie_name, 'comment_author_') === 0) {
unset($_COOKIE[$cookie_name]);
}
}
return $comments;
}
}
// Current user/visitor has pending comments
// set vary=2 for next time vary lookup
$this->add_commenter();
if ($this->conf(Base::O_CACHE_COMMENTER)) {
Control::set_private('existing commenter');
} else {
Control::set_nocache('existing commenter');
}
return $comments;
}
/**
* Check if default vary has a value
*
* @since 1.1.3
* @access public
*/
public static function has_vary()
{
if (empty($_COOKIE[self::$_vary_name])) {
return false;
}
return $_COOKIE[self::$_vary_name];
}
/**
* Append user status with logged in
*
* @since 1.1.3
* @since 1.6.2 Removed static referral
* @access public
*/
public function add_logged_in($logged_in_cookie = false, $expire = false, $expiration = false, $uid = false)
{
Debug2::debug('[Vary] add_logged_in');
/**
* NOTE: Run before `$this->_update_default_vary()` to make vary changeable
* @since 2.2.2
*/
self::can_ajax_vary();
// If the cookie is lost somehow, set it
$this->_update_default_vary($uid, $expire);
}
/**
* Remove user logged in status
*
* @since 1.1.3
* @since 1.6.2 Removed static referral
* @access public
*/
public function remove_logged_in()
{
Debug2::debug('[Vary] remove_logged_in');
/**
* NOTE: Run before `$this->_update_default_vary()` to make vary changeable
* @since 2.2.2
*/
self::can_ajax_vary();
// Force update vary to remove login status
$this->_update_default_vary(-1);
}
/**
* Allow vary can be changed for ajax calls
*
* @since 2.2.2
* @since 2.6 Changed to static
* @access public
*/
public static function can_ajax_vary()
{
Debug2::debug('[Vary] _can_change_vary -> true');
self::$_can_change_vary = true;
}
/**
* Check if can change default vary
*
* @since 1.6.2
* @access private
*/
private function can_change_vary()
{
// Don't change for ajax due to ajax not sending webp header
if (Router::is_ajax()) {
if (!self::$_can_change_vary) {
Debug2::debug('[Vary] can_change_vary bypassed due to ajax call');
return false;
}
}
/**
* POST request can set vary to fix #820789 login "loop" guest cache issue
* @since 1.6.5
*/
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST') {
Debug2::debug('[Vary] can_change_vary bypassed due to method not get/post');
return false;
}
/**
* Disable vary change if is from crawler
* @since 2.9.8 To enable woocommerce cart not empty warm up (@Taba)
*/
if (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], Crawler::FAST_USER_AGENT) === 0) {
Debug2::debug('[Vary] can_change_vary bypassed due to crawler');
return false;
}
if (!apply_filters('litespeed_can_change_vary', true)) {
Debug2::debug('[Vary] can_change_vary bypassed due to litespeed_can_change_vary hook');
return false;
}
return true;
}
/**
* Update default vary
*
* @since 1.6.2
* @since 1.6.6.1 Add ran check to make it only run once ( No run multiple times due to login process doesn't have valid uid )
* @access private
*/
private function _update_default_vary($uid = false, $expire = false)
{
// Make sure header output only run once
if (!defined('LITESPEED_DID_' . __FUNCTION__)) {
define('LITESPEED_DID_' . __FUNCTION__, true);
} else {
Debug2::debug2('[Vary] _update_default_vary bypassed due to run already');
return;
}
// If the cookie is lost somehow, set it
$vary = $this->finalize_default_vary($uid);
$current_vary = self::has_vary();
if ($current_vary !== $vary && $current_vary !== 'commenter' && $this->can_change_vary()) {
// $_COOKIE[ self::$_vary_name ] = $vary; // not needed
// save it
if (!$expire) {
$expire = time() + 2 * DAY_IN_SECONDS;
}
$this->_cookie($vary, $expire);
Debug2::debug("[Vary] set_cookie ---> $vary");
// Control::set_nocache( 'changing default vary' . " $current_vary => $vary" );
}
}
/**
* Get vary name
*
* @since 1.9.1
* @access public
*/
public function get_vary_name()
{
return self::$_vary_name;
}
/**
* Check if one user role is in vary group settings
*
* @since 1.2.0
* @since 3.0 Moved here from conf.cls
* @access public
* @param string $role The user role
* @return int The set value if already set
*/
public function in_vary_group($role)
{
$group = 0;
$vary_groups = $this->conf(Base::O_CACHE_VARY_GROUP);
$roles = explode(',', $role);
if ($found = array_intersect($roles, array_keys($vary_groups))) {
$groups = array();
foreach ($found as $curr_role) {
$groups[] = $vary_groups[$curr_role];
}
$group = implode(',', array_unique($groups));
} elseif (in_array('administrator', $roles)) {
$group = 99;
}
if ($group) {
Debug2::debug2('[Vary] role in vary_group [group] ' . $group);
}
return $group;
}
/**
* Finalize default Vary Cookie
*
* Get user vary tag based on admin_bar & role
*
* NOTE: Login process will also call this because it does not call wp hook as normal page loading
*
* @since 1.6.2
* @access public
*/
public function finalize_default_vary($uid = false)
{
// Must check this to bypass vary generation for guests
// Must check this to avoid Guest page's CSS/JS/CCSS/UCSS get non-guest vary filename
if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) {
return false;
}
$vary = array();
if ($this->conf(Base::O_GUEST)) {
$vary['guest_mode'] = 1;
}
if (!$uid) {
$uid = get_current_user_id();
} else {
Debug2::debug('[Vary] uid: ' . $uid);
}
// get user's group id
$role = Router::get_role($uid);
if ($uid > 0 && $role) {
$vary['logged-in'] = 1;
// parse role group from settings
if ($role_group = $this->in_vary_group($role)) {
$vary['role'] = $role_group;
}
// Get admin bar set
// see @_get_admin_bar_pref()
$pref = get_user_option('show_admin_bar_front', $uid);
Debug2::debug2('[Vary] show_admin_bar_front: ' . $pref);
$admin_bar = $pref === false || $pref === 'true';
if ($admin_bar) {
$vary['admin_bar'] = 1;
Debug2::debug2('[Vary] admin bar : true');
}
} else {
// Guest user
Debug2::debug('[Vary] role id: failed, guest');
}
/**
* Add filter
* @since 1.6 Added for Role Excludes for optimization cls
* @since 1.6.2 Hooked to webp (checked in v4, no webp anymore)
* @since 3.0 Used by 3rd hooks too
*/
$vary = apply_filters('litespeed_vary', $vary);
if (!$vary) {
return false;
}
ksort($vary);
$res = array();
foreach ($vary as $key => $val) {
$res[] = $key . ':' . $val;
}
$res = implode(';', $res);
if (defined('LSCWP_LOG')) {
return $res;
}
// Encrypt in production
return md5($this->conf(Base::HASH) . $res);
}
/**
* Get the hash of all vary related values
*
* @since 4.0
*/
public function finalize_full_varies()
{
$vary = $this->_finalize_curr_vary_cookies(true);
$vary .= $this->finalize_default_vary(get_current_user_id());
$vary .= $this->get_env_vary();
return $vary;
}
/**
* Get request environment Vary
*
* @since 4.0
*/
public function get_env_vary()
{
$env_vary = isset($_SERVER['LSCACHE_VARY_VALUE']) ? $_SERVER['LSCACHE_VARY_VALUE'] : false;
if (!$env_vary) {
$env_vary = isset($_SERVER['HTTP_X_LSCACHE_VARY_VALUE']) ? $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] : false;
}
return $env_vary;
}
/**
* Append user status with commenter
*
* This is ONLY used when submit a comment
*
* @since 1.1.6
* @access public
*/
public function append_commenter()
{
$this->add_commenter(true);
}
/**
* Correct user status with commenter
*
* @since 1.1.3
* @access private
* @param boolean $from_redirect If the request is from redirect page or not
*/
private function add_commenter($from_redirect = false)
{
// If the cookie is lost somehow, set it
if (self::has_vary() !== 'commenter') {
Debug2::debug('[Vary] Add commenter');
// $_COOKIE[ self::$_vary_name ] = 'commenter'; // not needed
// save it
// only set commenter status for current domain path
$this->_cookie('commenter', time() + apply_filters('comment_cookie_lifetime', 30000000), self::_relative_path($from_redirect));
// Control::set_nocache( 'adding commenter status' );
}
}
/**
* Remove user commenter status
*
* @since 1.1.3
* @access private
*/
private function remove_commenter()
{
if (self::has_vary() === 'commenter') {
Debug2::debug('[Vary] Remove commenter');
// remove logged in status from global var
// unset( $_COOKIE[ self::$_vary_name ] ); // not needed
// save it
$this->_cookie(false, false, self::_relative_path());
// Control::set_nocache( 'removing commenter status' );
}
}
/**
* Generate relative path for cookie
*
* @since 1.1.3
* @access private
* @param boolean $from_redirect If the request is from redirect page or not
*/
private static function _relative_path($from_redirect = false)
{
$path = false;
$tag = $from_redirect ? 'HTTP_REFERER' : 'SCRIPT_URL';
if (!empty($_SERVER[$tag])) {
$path = parse_url($_SERVER[$tag]);
$path = !empty($path['path']) ? $path['path'] : false;
Debug2::debug('[Vary] Cookie Vary path: ' . $path);
}
return $path;
}
/**
* Builds the vary header.
*
* NOTE: Non caccheable page can still set vary ( for logged in process )
*
* Currently, this only checks post passwords and 3rd party.
*
* @since 1.0.13
* @access public
* @global $post
* @return mixed false if the user has the postpass cookie. Empty string if the post is not password protected. Vary header otherwise.
*/
public function finalize()
{
// Finalize default vary
if (!defined('LITESPEED_GUEST') || !LITESPEED_GUEST) {
$this->_update_default_vary();
}
$tp_cookies = $this->_finalize_curr_vary_cookies();
if (!$tp_cookies) {
Debug2::debug2('[Vary] no custimzed vary');
return;
}
return self::X_HEADER . ': ' . implode(',', $tp_cookies);
}
/**
* Gets vary cookies or their values unique hash that are already added for the current page.
*
* @since 1.0.13
* @access private
* @return array List of all vary cookies currently added.
*/
private function _finalize_curr_vary_cookies($values_json = false)
{
global $post;
$cookies = array(); // No need to append default vary cookie name
if (!empty($post->post_password)) {
$postpass_key = 'wp-postpass_' . COOKIEHASH;
if ($this->_get_cookie_val($postpass_key)) {
Debug2::debug('[Vary] finalize bypassed due to password protected vary ');
// If user has password cookie, do not cache & ignore existing vary cookies
Control::set_nocache('password protected vary');
return false;
}
$cookies[] = $values_json ? $this->_get_cookie_val($postpass_key) : $postpass_key;
}
$cookies = apply_filters('litespeed_vary_curr_cookies', $cookies);
if ($cookies) {
$cookies = array_filter(array_unique($cookies));
Debug2::debug('[Vary] vary cookies changed by filter litespeed_vary_curr_cookies', $cookies);
}
if (!$cookies) {
return false;
}
// Format cookie name data or value data
sort($cookies); // This is to maintain the cookie val orders for $values_json=true case.
foreach ($cookies as $k => $v) {
$cookies[$k] = $values_json ? $this->_get_cookie_val($v) : 'cookie=' . $v;
}
return $values_json ? json_encode($cookies) : $cookies;
}
/**
* Get one vary cookie value
*
* @since 4.0
*/
private function _get_cookie_val($key)
{
if (!empty($_COOKIE[$key])) {
return $_COOKIE[$key];
}
return false;
}
/**
* Set the vary cookie.
*
* If vary cookie changed, must set non cacheable.
*
* @since 1.0.4
* @access private
* @param integer $val The value to update.
* @param integer $expire Expire time.
* @param boolean $path False if use wp root path as cookie path
*/
private function _cookie($val = false, $expire = false, $path = false)
{
if (!$val) {
$expire = 1;
}
/**
* Add HTTPS bypass in case clients use both HTTP and HTTPS version of site
* @since 1.7
*/
$is_ssl = $this->conf(Base::O_UTIL_NO_HTTPS_VARY) ? false : is_ssl();
setcookie(self::$_vary_name, $val, $expire, $path ?: COOKIEPATH, COOKIE_DOMAIN, $is_ssl, true);
}
}