* The core plugin router class.
* This generate the valid action.
defined('WPINC') || exit();
class Router extends Base {
const LOG_TAG = '[Router]';
const NONCE = 'LSCWP_NONCE';
const ACTION = 'LSCWP_CTRL';
const ACTION_SAVE_SETTINGS_NETWORK = 'save-settings-network';
const ACTION_DB_OPTM = 'db_optm';
const ACTION_PLACEHOLDER = 'placeholder';
const ACTION_AVATAR = 'avatar';
const ACTION_SAVE_SETTINGS = 'save-settings';
const ACTION_CLOUD = 'cloud';
const ACTION_IMG_OPTM = 'img_optm';
const ACTION_HEALTH = 'health';
const ACTION_CRAWLER = 'crawler';
const ACTION_PURGE = 'purge';
const ACTION_CONF = 'conf';
const ACTION_ACTIVATION = 'activation';
const ACTION_CSS = 'css';
const ACTION_UCSS = 'ucss';
const ACTION_VPI = 'vpi';
const ACTION_PRESET = 'preset';
const ACTION_IMPORT = 'import';
const ACTION_REPORT = 'report';
const ACTION_DEBUG2 = 'debug2';
const ACTION_CDN_CLOUDFLARE = 'CDN\Cloudflare';
const ACTION_ADMIN_DISPLAY = 'admin_display';
const ACTION_GUEST = 'guest';
const ACTION_TMP_DISABLE = 'tmp_disable';
// List all handlers here
private static $_HANDLERS = array(
self::ACTION_ADMIN_DISPLAY,
self::ACTION_CDN_CLOUDFLARE,
self::ACTION_PLACEHOLDER,
const TYPE = 'litespeed_type';
const ITEM_HASH = 'hash';
const ITEM_FLASH_HASH = 'flash_hash';
private static $_esi_enabled;
private static $_is_ajax;
private static $_is_logged_in;
private static $_is_admin_ip;
private static $_frontend_path;
* Redirect to self to continue operation
* Note: must return when use this func. CLI/Cron call won't die in this func.
public static function self_redirect( $action, $type ) {
if (defined('LITESPEED_CLI') || wp_doing_cron()) {
Admin_Display::success('To be continued'); // Show for CLI
// Add i to avoid browser too many redirected warning
$i = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0;
$link = Utility::build_url($action, $type, false, null, array( 'litespeed_i' => $i ));
$url = html_entity_decode($link);
exit("<meta http-equiv='refresh' content='0;url=$url'>");
* Check if can run optimize
* @since 2.3.1 Relocated from cdn.cls
public function can_optm() {
if (is_user_logged_in() && $this->conf(self::O_OPTM_GUEST_ONLY)) {
} elseif (is_preview()) {
} elseif (self::is_ajax()) {
if (self::_is_login_page()) {
Debug2::debug('[Router] Optm bypassed: login/reg page');
$can_final = apply_filters('litespeed_can_optm', $can);
if ($can_final != $can) {
Debug2::debug('[Router] Optm bypassed: filter');
* Check referer page to see if its from admin
public static function from_admin() {
return !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], get_admin_url()) === 0;
* Check if it can use CDN replacement
* @since 2.3.1 Relocated from cdn.cls
public static function can_cdn() {
Debug2::debug2('[Router] CDN bypassed: is not ajax call');
if (self::from_admin()) {
Debug2::debug2('[Router] CDN bypassed: ajax call from admin');
} elseif (is_preview()) {
* Bypass cron to avoid deregister jq notice `Do not deregister the <code>jquery-core</code> script in the administration area.`
if (self::_is_login_page()) {
Debug2::debug('[Router] CDN bypassed: login/reg page');
* Bypass post/page link setting
$rest_prefix = function_exists('rest_get_url_prefix') ? rest_get_url_prefix() : apply_filters('rest_url_prefix', 'wp-json');
!empty($_SERVER['REQUEST_URI']) &&
strpos($_SERVER['REQUEST_URI'], $rest_prefix . '/wp/v2/media') !== false &&
isset($_SERVER['HTTP_REFERER']) &&
strpos($_SERVER['HTTP_REFERER'], 'wp-admin') !== false
Debug2::debug('[Router] CDN bypassed: wp-json on admin page');
$can_final = apply_filters('litespeed_can_cdn', $can);
if ($can_final != $can) {
Debug2::debug('[Router] CDN bypassed: filter');
* Check if is login page or not
protected static function _is_login_page() {
if (in_array($GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ), true)) {
* UCSS/Crawler role simulator
* @since 3.3 Renamed from `is_crawler_role_simulation`
public function is_role_simulation() {
if (empty($_COOKIE['litespeed_hash']) && empty($_COOKIE['litespeed_flash_hash'])) {
self::debug('🪪 starting role validation');
// Check if is from crawler
// if ( empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) || strpos( $_SERVER[ 'HTTP_USER_AGENT' ], Crawler::FAST_USER_AGENT ) !== 0 ) {
// Debug2::debug( '[Router] user agent not match' );
$server_ip = $this->conf(self::O_SERVER_IP);
if (!$server_ip || self::get_ip() !== $server_ip) {
self::debug('❌❌ Role simulate uid denied! Not localhost visit!');
Control::set_nocache('Role simulate uid denied');
if (!empty($_COOKIE['litespeed_flash_hash'])) {
$hash_data = self::get_option(self::ITEM_FLASH_HASH, array());
if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) {
if (time() - $hash_data['ts'] < 120 && $_COOKIE['litespeed_flash_hash'] == $hash_data['hash']) {
self::debug('🪪 Role simulator flash hash matched, escalating user to be uid=' . $hash_data['uid']);
self::delete_option(self::ITEM_FLASH_HASH);
wp_set_current_user($hash_data['uid']);
if (!empty($_COOKIE['litespeed_hash'])) {
$hash_data = self::get_option(self::ITEM_HASH, array());
if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) {
$RUN_DURATION = $this->cls('Crawler')->get_crawler_duration();
if (time() - $hash_data['ts'] < $RUN_DURATION && $_COOKIE['litespeed_hash'] == $hash_data['hash']) {
self::debug('🪪 Role simulator hash matched, escalating user to be uid=' . $hash_data['uid']);
wp_set_current_user($hash_data['uid']);
self::debug('❌ WARNING: role simulator hash not match');
* Get a short ttl hash (2mins)
public function get_flash_hash( $uid ) {
$hash_data = self::get_option(self::ITEM_FLASH_HASH, array());
if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts'])) {
if (time() - $hash_data['ts'] < 60) {
return $hash_data['hash'];
// Check if this user has editor access or not
if (user_can($uid, 'edit_posts')) {
self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.');
self::update_option(self::ITEM_FLASH_HASH, array(
public function get_hash( $uid ) {
// Check if this user has editor access or not
if (user_can($uid, 'edit_posts')) {
self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.');
// As this is called only when starting crawling, not per page, no need to reuse
self::update_option(self::ITEM_HASH, array(
public static function get_role( $uid = null ) {
if (defined('LITESPEED_WP_ROLE')) {
return LITESPEED_WP_ROLE;
$uid = get_current_user_id();
$user = get_userdata($uid);
if (isset($user->roles) && is_array($user->roles)) {
$tmp = array_values($user->roles);
$role = implode(',', $tmp); // Combine for PHP5.3 const compatibility
Debug2::debug('[Router] get_role: ' . $role);
Debug2::debug('[Router] role: guest');
* The previous user init refactoring didn't fix this bcos this is in login process and the user role could change
* @see https://github.com/litespeedtech/lscache_wp/commit/69e7bc71d0de5cd58961bae953380b581abdc088
* @since 2.9.8 Won't assign const if in login process
if (substr_compare(wp_login_url(), $GLOBALS['pagenow'], -strlen($GLOBALS['pagenow'])) === 0) {
define('LITESPEED_WP_ROLE', $role);
return LITESPEED_WP_ROLE;
public static function frontend_path() {
// todo: move to htaccess.cls ?
if (!isset(self::$_frontend_path)) {
$frontend = rtrim(ABSPATH, '/'); // /home/user/public_html/frontend
// get home path failed. Trac ticket #37668 (e.g. frontend:/blog backend:/wordpress)
Debug2::debug('[Router] No ABSPATH, generating from home option');
$frontend = parse_url(get_option('home'));
$frontend = !empty($frontend['path']) ? $frontend['path'] : '';
$frontend = $_SERVER['DOCUMENT_ROOT'] . $frontend;
$frontend = realpath($frontend);
self::$_frontend_path = $frontend;
return self::$_frontend_path;
* Check if ESI is enabled or not
public function esi_enabled() {
if (!isset(self::$_esi_enabled)) {
self::$_esi_enabled = defined('LITESPEED_ON') && $this->conf(self::O_ESI);
if (!empty($_REQUEST[self::ACTION])) {
self::$_esi_enabled = false;
return self::$_esi_enabled;
* Check if crawler is enabled on server level
public static function can_crawl() {
if (isset($_SERVER['X-LSCACHE']) && strpos($_SERVER['X-LSCACHE'], 'crawler') === false) {
// CLI will bypass this check as crawler library can always do the 428 check
if (defined('LITESPEED_CLI')) {
public static function get_action() {
if (!isset(self::$_action)) {
self::cls()->verify_action();
defined('LSCWP_LOG') && Debug2::debug('[Router] LSCWP_CTRL verified: ' . var_export(self::$_action, true));
public static function is_logged_in() {
if (!isset(self::$_is_logged_in)) {
self::$_is_logged_in = is_user_logged_in();
return self::$_is_logged_in;
public static function is_ajax() {
if (!isset(self::$_is_ajax)) {
self::$_is_ajax = wp_doing_ajax();