* The admin-panel specific functionality of the plugin.
* Provides admin page rendering, notices, enqueueing of assets,
* menu registrations, and various admin utilities.
* @subpackage LiteSpeed/admin
* @author LiteSpeed Technologies <info@litespeedtech.com>
defined( 'WPINC' ) || exit();
* Handles WP-Admin UI for LiteSpeed Cache.
class Admin_Display extends Base {
* Log tag for Admin_Display.
* Notice class (info/blue).
const NOTICE_BLUE = 'notice notice-info';
* Notice class (success/green).
const NOTICE_GREEN = 'notice notice-success';
* Notice class (error/red).
const NOTICE_RED = 'notice notice-error';
* Notice class (warning/yellow).
const NOTICE_YELLOW = 'notice notice-warning';
* Option key for one-time messages.
const DB_MSG = 'messages';
* Option key for pinned messages.
const DB_MSG_PIN = 'msg_pin';
* Purge selection field name.
const PURGEBYOPT_SELECT = 'purgeby';
const PURGEBYOPT_LIST = 'purgebylist';
* Dismiss key for messages.
const DB_DISMISS_MSG = 'dismiss';
* Rule conflict flag (on).
const RULECONFLICT_ON = 'ExpiresDefault_1';
* Rule conflict dismissed flag.
const RULECONFLICT_DISMISSED = 'ExpiresDefault_0';
* Router type for QC hide banner.
const TYPE_QC_HIDE_BANNER = 'qc_hide_banner';
* Cookie name for QC hide banner.
const COOKIE_QC_HIDE_BANNER = 'litespeed_qc_hide_banner';
* Internal messages cache.
* @var array<string,string>
protected $messages = [];
* Cached default settings.
* @var array<string,mixed>
protected $default_settings = [];
* Whether current context is network admin.
protected $_is_network_admin = false;
* Whether multisite is enabled.
protected $_is_multisite = false;
* Incremental form submit button index.
* List of settings with filters and return type.
* @deprecated 7.7 Use general conf fitlers.
* @var array<string,array<string,mixed>>
protected static $settings_filters = [
'filter' => 'litespeed_crawler_disable_blocklist',
self::O_CRAWLER_LOAD_LIMIT => [
'filter' => [ Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE, Base::ENV_CRAWLER_LOAD_LIMIT ],
'filter' => 'litespeed_esi_nonces',
// Page Optimization - CSS.
'optm-ucss_per_pagetype' => [
'filter' => 'litespeed_ucss_per_pagetype',
// Page Optimization - Media.
self::O_MEDIA_ADD_MISSING_SIZES => [
'filter' => 'litespeed_media_ignore_remote_missing_sizes',
// Page Optimization - Media Exclude.
self::O_MEDIA_LAZY_EXC => [
'filter' => 'litespeed_media_lazy_img_excludes',
// Page Optimization - Tuning (JS).
self::O_OPTM_JS_DELAY_INC => [
'filter' => 'litespeed_optm_js_delay_inc',
'filter' => 'litespeed_optimize_js_excludes',
self::O_OPTM_JS_DEFER_EXC => [
'filter' => 'litespeed_optm_js_defer_exc',
self::O_OPTM_GM_JS_EXC => [
'filter' => 'litespeed_optm_gm_js_exc',
'filter' => 'litespeed_optm_uri_exc',
// Page Optimization - Tuning (CSS).
self::O_OPTM_CSS_EXC => [
'filter' => 'litespeed_optimize_css_excludes',
self::O_OPTM_UCSS_EXC => [
'filter' => 'litespeed_ucss_exc',
* Flat pages map: menu slug to template metadata.
* @var array<string,array{title:string,tpl:string,network?:bool}>
* Initialize the class and set its properties.
public function __construct() {
'litespeed' => [ 'title' => __( 'Dashboard', 'litespeed-cache' ), 'tpl' => 'dash/entry.tpl.php' ],
'litespeed-optimax' => [ 'title' => __( 'OptimaX', 'litespeed-cache' ), 'tpl' => 'optimax/entry.tpl.php', 'scope' => 'site' ],
'litespeed-presets' => [ 'title' => __( 'Presets', 'litespeed-cache' ), 'tpl' => 'presets/entry.tpl.php', 'scope' => 'site' ],
'litespeed-general' => [ 'title' => __( 'General', 'litespeed-cache' ), 'tpl' => 'general/entry.tpl.php' ],
'litespeed-cache' => [ 'title' => __( 'Cache', 'litespeed-cache' ), 'tpl' => 'cache/entry.tpl.php' ],
'litespeed-cdn' => [ 'title' => __( 'CDN', 'litespeed-cache' ), 'tpl' => 'cdn/entry.tpl.php', 'scope' => 'site' ],
'litespeed-img_optm' => [ 'title' => __( 'Image Optimization', 'litespeed-cache'), 'tpl' => 'img_optm/entry.tpl.php' ],
'litespeed-page_optm' => [ 'title' => __( 'Page Optimization', 'litespeed-cache' ), 'tpl' => 'page_optm/entry.tpl.php', 'scope' => 'site' ],
'litespeed-db_optm' => [ 'title' => __( 'Database', 'litespeed-cache' ), 'tpl' => 'db_optm/entry.tpl.php' ],
'litespeed-crawler' => [ 'title' => __( 'Crawler', 'litespeed-cache' ), 'tpl' => 'crawler/entry.tpl.php', 'scope' => 'site' ],
'litespeed-toolbox' => [ 'title' => __( 'Toolbox', 'litespeed-cache' ), 'tpl' => 'toolbox/entry.tpl.php' ],
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_style' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
$this->_is_network_admin = is_network_admin();
$this->_is_multisite = is_multisite();
$manage = ( $this->_is_multisite && $this->_is_network_admin ) ? 'manage_network_options' : 'manage_options';
if ( current_user_can( $manage ) ) {
add_action( 'wp_before_admin_bar_render', [ GUI::cls(), 'backend_shortcut' ] );
// `admin_notices` is after `admin_enqueue_scripts`.
add_action( $this->_is_network_admin ? 'network_admin_notices' : 'admin_notices', [ $this, 'display_messages' ] );
* In case this is called outside the admin page.
* @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
// add menus (Also check for mu-plugins)
if ( $this->_is_network_admin && ( is_plugin_active_for_network( LSCWP_BASENAME ) || defined( 'LSCWP_MU_PLUGIN' ) ) ) {
add_action( 'network_admin_menu', [ $this, 'register_admin_menu' ] );
add_action( 'admin_menu', [ $this, 'register_admin_menu' ] );
$this->cls( 'Metabox' )->register_settings();
* Echo a translated section title.
* @param string $id Language key.
public function title( $id ) {
echo wp_kses_post( Lang::title( $id ) );
* Bind per-page admin hooks for a given page hook.
* Adds footer text filter and preview banner when loading the page.
* @param string $hook Page hook suffix returned by add_*_page().
private function bind_page( $hook ) {
add_action( "load-$hook", function () {
function ( $footer_text ) {
$this->cls( 'Cloud' )->maybe_preview_banner();
require_once LSCWP_DIR . 'tpl/inc/admin_footer.php';
// Add unified body class for settings page and top-level page
add_filter( 'admin_body_class', function ( $classes ) {
$screen = get_current_screen();
if ( $screen && in_array( $screen->id, [ 'settings_page_litespeed-cache-options', 'toplevel_page_litespeed' ], true ) ) {
$classes .= ' litespeed-cache_page_litespeed';
* Render an admin page by slug using its mapped template file.
* @param string $slug The menu slug registered in $_pages.
private function render_page( $slug ) {
$tpl = LSCWP_DIR . 'tpl/' . $this->_pages[ $slug ]['tpl'];
is_file( $tpl ) ? require $tpl : wp_die( 'Template not found' );
* Register the admin menu display.
public function register_admin_menu() {
$capability = $this->_is_network_admin ? 'manage_network_options' : 'manage_options';
$scope = $this->_is_network_admin ? 'network' : 'site';
foreach ( $this->_pages as $slug => $meta ) {
if ( 'litespeed-optimax' === $slug && !defined( 'LITESPEED_OX' ) ) {
if ( ! empty( $meta['scope'] ) && $meta['scope'] !== $scope ) {
$hook = add_submenu_page(
function () use ( $slug ) {
$this->render_page( $slug );
$this->bind_page( $hook );
// sub menus under options.
$hook = add_options_page(
'litespeed-cache-options',
$this->render_page( 'litespeed-cache' );
$this->bind_page( $hook );
* Register the stylesheets for the admin area.
public function enqueue_style() {
wp_enqueue_style( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', [], Core::VER, 'all' );
wp_enqueue_style( Core::PLUGIN_NAME . '-dark-mode', LSWCP_PLUGIN_URL . 'assets/css/litespeed-dark-mode.css', [], Core::VER, 'all' );
* Register/enqueue the JavaScript for the admin area.
* @since 7.3 Added deactivation modal code.
public function enqueue_scripts() {
wp_register_script( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/litespeed-cache-admin.js', [], Core::VER, true );
if ( GUI::has_whm_msg() ) {
$ajax_url_dismiss_whm = Utility::build_url( Core::ACTION_DISMISS, GUI::TYPE_DISMISS_WHM, true );
$localize_data['ajax_url_dismiss_whm'] = $ajax_url_dismiss_whm;
if ( GUI::has_msg_ruleconflict() ) {
$ajax_url = Utility::build_url( Core::ACTION_DISMISS, GUI::TYPE_DISMISS_EXPIRESDEFAULT, true );
$localize_data['ajax_url_dismiss_ruleconflict'] = $ajax_url;
// Injection to LiteSpeed pages
$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( 'admin.php' === $pagenow && $page && ( 0 === strpos( $page, 'litespeed-' ) || 'litespeed' === $page ) ) {
if ( in_array( $page, [ 'litespeed-crawler', 'litespeed-cdn' ], true ) ) {
// Babel JS type correction
add_filter( 'script_loader_tag', [ $this, 'babel_type' ], 10, 3 );
wp_enqueue_script( Core::PLUGIN_NAME . '-lib-react', LSWCP_PLUGIN_URL . 'assets/js/react.min.js', [], Core::VER, false );
wp_enqueue_script( Core::PLUGIN_NAME . '-lib-babel', LSWCP_PLUGIN_URL . 'assets/js/babel.min.js', [], Core::VER, false );
// Crawler Cookie Simulation
if ( 'litespeed-crawler' === $page ) {
wp_enqueue_script( Core::PLUGIN_NAME . '-crawler', LSWCP_PLUGIN_URL . 'assets/js/component.crawler.js', [], Core::VER, false );
$localize_data['lang'] = [];
$localize_data['lang']['cookie_name'] = __( 'Cookie Name', 'litespeed-cache' );
$localize_data['lang']['cookie_value'] = __( 'Cookie Values', 'litespeed-cache' );
$localize_data['lang']['one_per_line'] = Doc::one_per_line( true );
$localize_data['lang']['remove_cookie_simulation'] = __( 'Remove cookie simulation', 'litespeed-cache' );
$localize_data['lang']['add_cookie_simulation_row'] = __( 'Add new cookie to simulate', 'litespeed-cache' );
if ( empty( $localize_data['ids'] ) ) {
$localize_data['ids'] = [];
$localize_data['ids']['crawler_cookies'] = self::O_CRAWLER_COOKIES;
if ( 'litespeed-cdn' === $page ) {
$home_url = home_url( '/' );
$parsed = wp_parse_url( $home_url );
if ( ! empty( $parsed['scheme'] ) ) {
$home_url = str_replace( $parsed['scheme'] . ':', '', $home_url );
$cdn_url = 'https://cdn.' . substr( $home_url, 2 );
wp_enqueue_script( Core::PLUGIN_NAME . '-cdn', LSWCP_PLUGIN_URL . 'assets/js/component.cdn.js', [], Core::VER, false );
$localize_data['lang'] = [];
$localize_data['lang']['cdn_mapping_url'] = Lang::title( self::CDN_MAPPING_URL );
$localize_data['lang']['cdn_mapping_inc_img'] = Lang::title( self::CDN_MAPPING_INC_IMG );
$localize_data['lang']['cdn_mapping_inc_css'] = Lang::title( self::CDN_MAPPING_INC_CSS );
$localize_data['lang']['cdn_mapping_inc_js'] = Lang::title( self::CDN_MAPPING_INC_JS );
$localize_data['lang']['cdn_mapping_filetype'] = Lang::title( self::CDN_MAPPING_FILETYPE );
$localize_data['lang']['cdn_mapping_url_desc'] = sprintf( __( 'CDN URL to be used. For example, %s', 'litespeed-cache' ), '<code>' . esc_html( $cdn_url ) . '</code>' );
$localize_data['lang']['one_per_line'] = Doc::one_per_line( true );
$localize_data['lang']['cdn_mapping_remove'] = __( 'Remove CDN URL', 'litespeed-cache' );
$localize_data['lang']['add_cdn_mapping_row'] = __( 'Add new CDN URL', 'litespeed-cache' );
$localize_data['lang']['on'] = __( 'ON', 'litespeed-cache' );
$localize_data['lang']['off'] = __( 'OFF', 'litespeed-cache' );
if ( empty( $localize_data['ids'] ) ) {
$localize_data['ids'] = [];
$localize_data['ids']['cdn_mapping'] = self::O_CDN_MAPPING;
// Load iziModal JS and CSS
$show_deactivation_modal = ( is_multisite() && ! is_network_admin() ) ? false : true;
if ( $show_deactivation_modal && 'plugins.php' === $pagenow ) {
wp_enqueue_script( Core::PLUGIN_NAME . '-iziModal', LSWCP_PLUGIN_URL . 'assets/js/iziModal.min.js', [], Core::VER, true );
wp_enqueue_style( Core::PLUGIN_NAME . '-iziModal', LSWCP_PLUGIN_URL . 'assets/css/iziModal.min.css', [], Core::VER, 'all' );
add_action( 'admin_footer', [ $this, 'add_deactivation_html' ] );