* The object cache class.
defined( 'WPINC' ) || exit();
require_once dirname( __DIR__ ) . '/autoload.php';
* Object cache handler using Redis or Memcached.
* NOTE: this class may be included without initialized core.
class Object_Cache extends Root {
const LOG_TAG = '[Object_Cache]';
* Object cache enable key.
const O_OBJECT = 'object';
* Object kind (Redis/Memcached).
const O_OBJECT_KIND = 'object-kind';
const O_OBJECT_HOST = 'object-host';
const O_OBJECT_PORT = 'object-port';
const O_OBJECT_LIFE = 'object-life';
* Persistent connection flag.
const O_OBJECT_PERSISTENT = 'object-persistent';
const O_OBJECT_ADMIN = 'object-admin';
const O_OBJECT_TRANSIENTS = 'object-transients';
const O_OBJECT_DB_ID = 'object-db_id';
const O_OBJECT_USER = 'object-user';
const O_OBJECT_PSWD = 'object-pswd';
const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups';
* Non-persistent groups list.
const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups';
* @var \Redis|\Memcached|null
* True => Redis, false => Memcached.
* Use persistent connection.
private $_cfg_persistent;
private $_cfg_transients;
* Default TTL in seconds.
private $_default_life = 360;
* 'Redis' or 'Memcached'.
private $_oc_driver = 'Memcached'; // Redis or Memcached.
private $_global_groups = [];
private $_non_persistent_groups = [];
* NOTE: this class may be included without initialized core.
* @param array|false $cfg Optional configuration to bootstrap without core.
public function __construct( $cfg = false ) {
if ( ! is_array( $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] ) ) {
$cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] );
if ( ! is_array( $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ) {
$cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] );
$this->_cfg_debug = $cfg[ Base::O_DEBUG ] ? $cfg[ Base::O_DEBUG ] : false;
$this->_cfg_method = $cfg[ Base::O_OBJECT_KIND ] ? true : false;
$this->_cfg_host = $cfg[ Base::O_OBJECT_HOST ];
$this->_cfg_port = $cfg[ Base::O_OBJECT_PORT ];
$this->_cfg_life = $cfg[ Base::O_OBJECT_LIFE ];
$this->_cfg_persistent = $cfg[ Base::O_OBJECT_PERSISTENT ];
$this->_cfg_admin = $cfg[ Base::O_OBJECT_ADMIN ];
$this->_cfg_transients = $cfg[ Base::O_OBJECT_TRANSIENTS ];
$this->_cfg_db = $cfg[ Base::O_OBJECT_DB_ID ];
$this->_cfg_user = $cfg[ Base::O_OBJECT_USER ];
$this->_cfg_pswd = $cfg[ Base::O_OBJECT_PSWD ];
$this->_global_groups = $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ];
$this->_non_persistent_groups = $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ];
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
$this->_cfg_enabled = $cfg[ Base::O_OBJECT ] && class_exists( $this->_oc_driver ) && $this->_cfg_host;
} elseif ( defined( 'LITESPEED_CONF_LOADED' ) ) { // If OC is OFF, will hit here to init OC after conf initialized
$this->_cfg_debug = $this->conf( Base::O_DEBUG ) ? $this->conf( Base::O_DEBUG ) : false;
$this->_cfg_method = $this->conf( Base::O_OBJECT_KIND ) ? true : false;
$this->_cfg_host = $this->conf( Base::O_OBJECT_HOST );
$this->_cfg_port = $this->conf( Base::O_OBJECT_PORT );
$this->_cfg_life = $this->conf( Base::O_OBJECT_LIFE );
$this->_cfg_persistent = $this->conf( Base::O_OBJECT_PERSISTENT );
$this->_cfg_admin = $this->conf( Base::O_OBJECT_ADMIN );
$this->_cfg_transients = $this->conf( Base::O_OBJECT_TRANSIENTS );
$this->_cfg_db = $this->conf( Base::O_OBJECT_DB_ID );
$this->_cfg_user = $this->conf( Base::O_OBJECT_USER );
$this->_cfg_pswd = $this->conf( Base::O_OBJECT_PSWD );
$this->_global_groups = $this->conf( Base::O_OBJECT_GLOBAL_GROUPS );
$this->_non_persistent_groups = $this->conf( Base::O_OBJECT_NON_PERSISTENT_GROUPS );
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
$this->_cfg_enabled = $this->conf( Base::O_OBJECT ) && class_exists( $this->_oc_driver ) && $this->_cfg_host;
} elseif ( defined( 'self::CONF_FILE' ) && file_exists( WP_CONTENT_DIR . '/' . self::CONF_FILE ) ) {
// Get cfg from _data_file.
// Use self::const to avoid loading more classes.
$cfg = \json_decode( file_get_contents( WP_CONTENT_DIR . '/' . self::CONF_FILE ), true );
if ( ! empty( $cfg[ self::O_OBJECT_HOST ] ) ) {
$this->_cfg_debug = ! empty( $cfg[ Base::O_DEBUG ] ) ? $cfg[ Base::O_DEBUG ] : false;
$this->_cfg_method = ! empty( $cfg[ self::O_OBJECT_KIND ] ) ? $cfg[ self::O_OBJECT_KIND ] : false;
$this->_cfg_host = $cfg[ self::O_OBJECT_HOST ];
$this->_cfg_port = $cfg[ self::O_OBJECT_PORT ];
$this->_cfg_life = ! empty( $cfg[ self::O_OBJECT_LIFE ] ) ? $cfg[ self::O_OBJECT_LIFE ] : $this->_default_life;
$this->_cfg_persistent = ! empty( $cfg[ self::O_OBJECT_PERSISTENT ] ) ? $cfg[ self::O_OBJECT_PERSISTENT ] : false;
$this->_cfg_admin = ! empty( $cfg[ self::O_OBJECT_ADMIN ] ) ? $cfg[ self::O_OBJECT_ADMIN ] : false;
$this->_cfg_transients = ! empty( $cfg[ self::O_OBJECT_TRANSIENTS ] ) ? $cfg[ self::O_OBJECT_TRANSIENTS ] : false;
$this->_cfg_db = ! empty( $cfg[ self::O_OBJECT_DB_ID ] ) ? $cfg[ self::O_OBJECT_DB_ID ] : 0;
$this->_cfg_user = ! empty( $cfg[ self::O_OBJECT_USER ] ) ? $cfg[ self::O_OBJECT_USER ] : '';
$this->_cfg_pswd = ! empty( $cfg[ self::O_OBJECT_PSWD ] ) ? $cfg[ self::O_OBJECT_PSWD ] : '';
$this->_global_groups = ! empty( $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] ) ? $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] : [];
$this->_non_persistent_groups = ! empty( $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ? $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] : [];
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
$this->_cfg_enabled = class_exists( $this->_oc_driver ) && $this->_cfg_host;
$this->_cfg_enabled = false;
$this->_cfg_enabled = false;
* @param string $text Log text.
private function debug_oc( $text ) {
if ( defined( 'LSCWP_LOG' ) ) {
if ( Base::VAL_ON2 !== $this->_cfg_debug ) {
$litespeed_data_folder = defined( 'LITESPEED_DATA_FOLDER' ) ? LITESPEED_DATA_FOLDER : 'litespeed';
$lscwp_content_dir = defined( 'LSCWP_CONTENT_DIR' ) ? LSCWP_CONTENT_DIR : WP_CONTENT_DIR;
$litespeed_static_dir = $lscwp_content_dir . '/' . $litespeed_data_folder;
$log_path_prefix = $litespeed_static_dir . '/debug/';
$log_file = $log_path_prefix . Debug2::FilePath( 'debug' );
if ( file_exists( $log_path_prefix . 'index.php' ) && file_exists( $log_file ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log(gmdate('m/d/y H:i:s') . ' - OC - ' . $text . PHP_EOL, 3, $log_file);
* Get `Store Transients` setting value.
* @param string $group Group name.
public function store_transients( $group ) {
return $this->_cfg_transients && $this->_is_transients_group( $group );
* Check if the group belongs to transients or not.
* @param string $group Group name.
private function _is_transients_group( $group ) {
return in_array( $group, [ 'transient', 'site-transient' ], true );
* Update WP object cache file config.
* @param array $options Options to apply after update.
public function update_file( $options ) {
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
if ( ! file_exists( $_oc_wp_file ) || md5_file( $_oc_wp_file ) !== md5_file( $_oc_ori_file ) ) {
$this->debug_oc( 'copying object-cache.php file to ' . $_oc_wp_file );
copy( $_oc_ori_file, $_oc_wp_file );
$this->_reconnect( $options );
* Remove object cache file.
public function del_file() {
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
if ( file_exists( $_oc_wp_file ) && md5_file( $_oc_wp_file ) === md5_file( $_oc_ori_file ) ) {
$this->debug_oc( 'removing ' . $_oc_wp_file );
wp_delete_file( $_oc_wp_file );
* Try to build connection.
* @return bool|null False on failure, true on success, null if unsupported.
public function test_connection() {
return $this->_connect();
* Force to connect with this setting.
* @param array $cfg Reconnect configuration.
private function _reconnect( $cfg ) {
$this->debug_oc( 'Reconnecting' );
if ( isset( $this->_conn ) ) {
// error_log( 'Object: Quitting existing connection!' );
$this->debug_oc( 'Quitting existing connection' );
$this->cls( false, true );
$cls = $this->cls( false, false, $cfg );
if ( isset( $cls->_conn ) ) {
* Connect to Memcached/Redis server.
* @return bool|null False on failure, true on success, null if driver missing.
private function _connect() {
if ( isset( $this->_conn ) ) {
// error_log( 'Object: _connected' );
if ( ! class_exists( $this->_oc_driver ) || ! $this->_cfg_host ) {
$this->debug_oc( '_oc_driver cls non existed or _cfg_host missed: ' . $this->_oc_driver . ' [_cfg_host] ' . $this->_cfg_host . ':' . $this->_cfg_port );
if ( defined( 'LITESPEED_OC_FAILURE' ) ) {
$this->debug_oc( 'LITESPEED_OC_FAILURE const defined' );
$this->debug_oc( 'Init ' . $this->_oc_driver . ' connection to ' . $this->_cfg_host . ':' . $this->_cfg_port );