File "vary.cls.php"
Full Path: /home/ycoalition/public_html/blog/wp-admin/js/widgets/plugins/litespeed-cache/src/vary.cls.php
File size: 19.29 KB
MIME-type: text/x-php
Charset: utf-8
<?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;
}
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 );
if ( array_key_exists( $role, $vary_groups ) ) {
$group = $vary_groups[ $role ];
}
elseif ( $role === 'administrator' ) {
$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 );
}
}