* The plugin activation class.
* @since 1.5 Moved into /inc
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
defined( 'WPINC' ) || exit();
* Handles plugin activation, deactivation, and related file management.
class Activation extends Base {
const TYPE_UPGRADE = 'upgrade';
const TYPE_INSTALL_3RD = 'install_3rd';
const TYPE_INSTALL_ZIP = 'install_zip';
const TYPE_DISMISS_RECOMMENDED = 'dismiss_recommended';
const NETWORK_TRANSIENT_COUNT = 'lscwp_network_count';
* Data file path for configuration.
private static $data_file;
* Initializes the data file path.
public function __construct() {
self::$data_file = LSCWP_CONTENT_DIR . '/' . self::CONF_FILE;
* The activation hook callback.
* Handles plugin activation tasks, including file creation and multisite setup.
public static function register_activation() {
! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Activate_' . get_current_blog_id() );
/* Network file handler */
$count = self::get_network_count();
if ( false !== $count ) {
$count = (int) $count + 1;
set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS );
if ( ! is_network_admin() ) {
// Only itself is activated, set .htaccess with only CacheLookUp
Htaccess::cls()->insert_ls_wrapper();
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
self::cls()->update_files();
if ( defined( 'LSCWP_REF' ) && 'whm' === LSCWP_REF ) {
GUI::update_option( GUI::WHM_MSG, GUI::WHM_MSG_VAL );
* Removes all LiteSpeed Cache settings and data.
* @since 7.3 Updated to remove all settings.
public static function uninstall_litespeed_cache() {
$current_blog = get_current_blog_id();
$sub_sites = get_sites();
foreach ( $sub_sites as $sub_site ) {
$sub_blog_id = (int) $sub_site->blog_id;
if ( $sub_blog_id !== $current_blog ) {
switch_to_blog( $sub_blog_id );
Data::cls()->tables_del();
switch_to_blog( $current_blog );
// Delete current blog/site
Data::cls()->tables_del();
if ( file_exists( LITESPEED_STATIC_DIR ) ) {
File::rrmdir( LITESPEED_STATIC_DIR );
Cloud::version_check( 'uninstall' );
* Remove all litespeed settings.
* Deletes all LiteSpeed Cache options from the database.
private static function delete_settings() {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$wpdb->query($wpdb->prepare("DELETE FROM `$wpdb->options` WHERE option_name LIKE %s", 'litespeed.%'));
* Get the blog ids for the network. Accepts function arguments.
* @param array $args Arguments for get_sites().
* @return array The array of blog ids.
public static function get_network_ids( $args = [] ) {
$blogs = get_sites( $args );
* Gets the count of active litespeed cache plugins on multisite.
* @return int|false Number of active LSCWP or false if none.
private static function get_network_count() {
$count = get_site_transient( self::NETWORK_TRANSIENT_COUNT );
if ( false !== $count ) {
$sites = self::get_network_ids( [ 'deleted' => 0 ] );
foreach ( $sites as $site ) {
$bid = is_object( $site ) && property_exists( $site, 'blog_id' ) ? $site->blog_id : $site;
$plugins = get_blog_option( $bid, 'active_plugins', $default );
if ( ! empty( $plugins ) && in_array( LSCWP_BASENAME, $plugins, true ) ) {
* 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';
if ( is_plugin_active_for_network( LSCWP_BASENAME ) ) {
* Is this deactivate call the last active installation on the multisite network?
private static function is_deactivate_last() {
$count = self::get_network_count();
if ( false === $count ) {
// Not deactivating the last one.
set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS );
delete_site_transient( self::NETWORK_TRANSIENT_COUNT );
* The deactivation hook callback.
* Initializes all clean up functionalities.
public static function register_deactivation() {
! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Deactivate_' . get_current_blog_id() );
if ( ! self::is_deactivate_last() ) {
if ( is_network_admin() ) {
// Still other activated subsite left, set .htaccess with only CacheLookUp
Htaccess::cls()->insert_ls_wrapper();
} catch ( \Exception $ex ) {
Admin_Display::error($ex->getMessage());
self::cls()->manage_wp_cache_const( false );
} catch ( \Exception $ex ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( 'In wp-config.php: WP_CACHE could not be set to false during deactivation!' );
Admin_Display::error( $ex->getMessage() );
/* 2) adv-cache.php; Dropped in v3.0.4 */
/* 3) object-cache.php; */
Object_Cache::cls()->del_file();
Htaccess::cls()->clear_rules();
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
/* 5) .litespeed_conf.dat; */
self::del_conf_data_file();
/* 6) delete option lscwp_whm_install */
// delete in case it's not deleted prior to deactivation.
* Manage related files based on plugin latest conf
* 5) .litespeed_conf.dat;
public function update_files() {
Debug2::debug( '🗂️ [Activation] update_files' );
// Update cache setting `_CACHE`
$this->cls( 'Conf' )->define_cache();
// Site options applied already
$options = $this->get_options();
$this->manage_wp_cache_const( $options[ self::_CACHE ] );
} catch ( \Exception $ex ) {
// Add msg to admin page or CLI
Admin_Display::error( wp_kses_post( $ex->getMessage() ) );
/* 2) adv-cache.php; Dropped in v3.0.4 */
/* 3) object-cache.php; */
if ( $options[ self::O_OBJECT ] && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) {
$this->cls( 'Object_Cache' )->update_file( $options );
$this->cls( 'Object_Cache' )->del_file(); // Note: because it doesn't reconnect, which caused setting page OC option changes delayed, thus may meet Connect Test Failed issue (Next refresh will correct it). Not a big deal, will keep as is.
$this->cls( 'Htaccess' )->update( $options );
} catch ( \Exception $ex ) {
Admin_Display::error( wp_kses_post( $ex->getMessage() ) );
/* 5) .litespeed_conf.dat; */
if ( ( $options[ self::O_GUEST ] || $options[ self::O_OBJECT ] ) && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) {
$this->update_conf_data_file( $options );
* Removes the .litespeed_conf.dat file.
private static function del_conf_data_file() {
if ( ! $wp_filesystem ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
if ( $wp_filesystem->exists( self::$data_file ) ) {
$wp_filesystem->delete( self::$data_file );
* Update data conf file for guest mode & object cache
* Updates the .litespeed_conf.dat file with relevant settings.
* @param array $options Plugin options.
private function update_conf_data_file( $options ) {
if ( $options[ self::O_OBJECT ] ) {
self::O_OBJECT_PERSISTENT,
self::O_OBJECT_TRANSIENTS,
self::O_OBJECT_GLOBAL_GROUPS,
self::O_OBJECT_NON_PERSISTENT_GROUPS,
$ids = array_merge( $ids, $this_ids );
if ( $options[ self::O_GUEST ] ) {
self::O_CACHE_LOGIN_COOKIE,
self::O_UTIL_NO_HTTPS_VARY,
$ids = array_merge( $ids, $this_ids );
$data[ $v ] = $options[ $v ];
$data = wp_json_encode( $data );
$old_data = File::read( self::$data_file );
if ( $old_data !== $data ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Activation] Updating .litespeed_conf.dat' );
File::save( self::$data_file, $data );
* Update the WP_CACHE variable in the wp-config.php file.
* If enabling, check if the variable is defined, and if not, define it.
* Vice versa for disabling.
* @param bool $enable Whether to enable WP_CACHE.
* @throws \Exception If wp-config.php cannot be modified.
* @return bool True if updated, false if no change needed.
public function manage_wp_cache_const( $enable ) {
if ( defined( 'WP_CACHE' ) && WP_CACHE ) {
} elseif ( ! defined( 'WP_CACHE' ) || ( defined( 'WP_CACHE' ) && ! WP_CACHE ) ) {
if ( apply_filters( 'litespeed_wpconfig_readonly', false ) ) {
throw new \Exception( 'wp-config file is forbidden to modify due to API hook: litespeed_wpconfig_readonly' );
* Follow WP's logic to locate wp-config file
$conf_file = ABSPATH . 'wp-config.php';
if ( ! file_exists( $conf_file ) ) {
$conf_file = dirname( ABSPATH ) . '/wp-config.php';
$content = File::read( $conf_file );
throw new \Exception( 'wp-config file content is empty: ' . wp_kses_post( $conf_file ) );
// Remove the line `define('WP_CACHE', true/false);` first
if ( defined( 'WP_CACHE' ) ) {
$content = preg_replace( '/define\(\s*([\'"])WP_CACHE\1\s*,\s*\w+\s*\)\s*;/sU', '', $content );
$content = preg_replace( '/^<\?php/', "<?php\ndefine( 'WP_CACHE', true );", $content );
$res = File::save( $conf_file, $content, false, false, false );
throw new \Exception( 'wp-config.php operation failed when changing `WP_CACHE` const: ' . wp_kses_post( $res ) );
* Enables auto-updates for the plugin if configured.
public function auto_update() {
if ( ! $this->conf( Base::O_AUTO_UPGRADE ) ) {