| Server IP : 208.122.213.31 / Your IP : 216.73.216.45 Web Server : Apache System : Linux msd6191.mjhst.com 4.18.0-553.137.1.el8_10.x86_64 #1 SMP Wed Jun 24 11:40:24 UTC 2026 x86_64 User : WHMCS_MIA_382 ( 1001) PHP Version : 7.4.33 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/httpd/html/baberankings.com/wp-content/plugins/w3-total-cache/ |
Upload File : |
<?php
/**
* File: Cli.php
*
* @package W3TC
*/
namespace W3TC;
/**
* Class Config
*
* Provides configuration data using cache
*
* phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
* phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
* phpcs:disable WordPress.WP.AlternativeFunctions
* phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
*/
class Config {
/**
* Blog ID of loaded config
*
* @var integer
*/
private $_blog_id;
/**
* Is master flag
*
* @var bool
*/
private $_is_master;
/**
* Is this preview config
*
* @var boolean
*/
private $_preview;
/**
* First 20 digits of data MD5
*
* @var integer
*/
private $_md5;
/**
* Data
*
* @var array
*/
private $_data;
/**
* Compiled flag
*
* @var bool
*/
private $_compiled;
/**
* Retrieves a configuration array from cache storage if enabled and present, otherwise retrieves from
* database/file via _util_array_from_storage private method
*
* @param int $blog_id The ID of the blog.
* @param bool $preview Whether to load the preview configuration.
*
* @return array|null The configuration array or null if not found.
*/
public static function util_array_from_storage( $blog_id, $preview ) {
if ( ! defined( 'W3TC_CONFIG_CACHE_ENGINE' ) ) {
return self::_util_array_from_storage( $blog_id, $preview );
}
// config cache enabled.
$w3tc_config = ConfigCache::util_array_from_storage( $blog_id, $preview );
if ( ! is_null( $w3tc_config ) ) {
return $w3tc_config;
}
$w3tc_config = self::_util_array_from_storage( $blog_id, $preview );
ConfigCache::save_item( $blog_id, $preview, $w3tc_config );
return $w3tc_config;
}
/**
* Retrieves a configuration array from database/file storage.
*
* @param int $blog_id The ID of the blog.
* @param bool $preview Whether to load the preview configuration.
*
* @return array|null The configuration array or null if not found.
*/
private static function _util_array_from_storage( $blog_id, $preview ) {
if ( defined( 'W3TC_CONFIG_DATABASE' ) && W3TC_CONFIG_DATABASE ) {
return ConfigDbStorage::util_array_from_storage( $blog_id, $preview );
}
$filename = self::util_config_filename( $blog_id, $preview );
if ( file_exists( $filename ) && is_readable( $filename ) ) {
/**
* Including file directly instead of read+eval causes constant problems with APC, ZendCache, and
* WSOD in a case of broken config file.
*/
$content = @file_get_contents( $filename );
$w3tc_config = @json_decode( substr( $content, 14 ), true );
if ( is_array( $w3tc_config ) ) {
return $w3tc_config;
}
}
return null;
}
/**
* Retrieves the filename for the configuration.
*
* @param int $blog_id The ID of the blog.
* @param bool $preview Whether to load the preview configuration.
*
* @return string The configuration file path.
*/
public static function util_config_filename( $blog_id, $preview ) {
$postfix = ( $preview ? '-preview' : '' ) . '.php';
if ( $blog_id <= 0 ) {
$filename = W3TC_CONFIG_DIR . '/master' . $postfix;
} else {
$filename = W3TC_CONFIG_DIR . '/' . sprintf( '%06d', $blog_id ) . $postfix;
}
$d = w3tc_apply_filters(
'config_filename',
array(
'blog_id' => $blog_id,
'preview' => $preview,
'filename' => $filename,
)
);
return $d['filename'];
}
/**
* Retrieves the legacy configuration filename.
*
* @param int $blog_id The ID of the blog.
* @param bool $preview Whether to load the preview configuration.
*
* @return string The legacy configuration file path.
*/
public static function util_config_filename_legacy_v2( $blog_id, $preview ) {
$postfix = ( $preview ? '-preview' : '' ) . '.json';
if ( $blog_id <= 0 ) {
return W3TC_CONFIG_DIR . '/master' . $postfix;
} else {
return W3TC_CONFIG_DIR . '/' . sprintf( '%06d', $blog_id ) . $postfix;
}
}
/**
* Constructor to initialize configuration for a given blog.
*
* @param int|null $blog_id The ID of the blog, or null to determine based on environment.
*/
public function __construct( $blog_id = null ) {
if ( ! is_null( $blog_id ) ) {
$this->_blog_id = $blog_id;
$this->_is_master = ( 0 === $this->_blog_id );
} else {
if ( Util_Environment::is_using_master_config() ) {
$this->_blog_id = 0;
} else {
$this->_blog_id = Util_Environment::blog_id();
}
$this->_is_master = ( 0 === Util_Environment::blog_id() );
}
$this->_preview = Util_Environment::is_preview_mode();
$this->load();
}
/**
* Retrieves a configuration value for a given key or returns cached/uncached default value if not found.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param mixed $default_value The default value to return if the key is not found.
*
* @return mixed The configuration value, or the default if not found.
*/
public function get( $w3tc_key, $default_value = null ) {
$v = $this->_get( $this->_data, $w3tc_key );
if ( ! is_null( $v ) ) {
return $v;
}
// take default value.
if ( ! empty( $default_value ) || ! function_exists( 'apply_filters' ) ) {
return $default_value;
}
// try cached default values.
static $default_values = null;
if ( is_null( $default_values ) ) {
$default_values = apply_filters( 'w3tc_config_default_values', array() );
}
$v = $this->_get( $default_values, $w3tc_key );
if ( ! is_null( $v ) ) {
return $v;
}
// update default values.
$default_values = apply_filters( 'w3tc_config_default_values', array() );
$v = $this->_get( $default_values, $w3tc_key );
if ( ! is_null( $v ) ) {
return $v;
}
return $default_value;
}
/**
* Retrieves a configuration value for a given key.
*
* @param array $w3tc_a The array to search in.
* @param string $w3tc_key The key to look up in the array.
*
* @return mixed The configuration value, or null if not found.
*/
private function _get( &$w3tc_a, $w3tc_key ) {
if ( is_array( $w3tc_key ) ) {
$key0 = $w3tc_key[0];
if ( isset( $w3tc_a[ $key0 ] ) ) {
$key1 = $w3tc_key[1];
if ( isset( $w3tc_a[ $key0 ][ $key1 ] ) ) {
self::_maybe_lazy_decrypt( $w3tc_a[ $key0 ][ $key1 ] );
return $w3tc_a[ $key0 ][ $key1 ];
}
}
} elseif ( isset( $w3tc_a[ $w3tc_key ] ) ) {
self::_maybe_lazy_decrypt( $w3tc_a[ $w3tc_key ] );
return $w3tc_a[ $w3tc_key ];
}
return null;
}
/**
* Lazily decrypts an `enc:v1:...` envelope value in place on first read.
*
* Companion to the early-bootstrap skip in {@see self::decrypt_secrets()}:
* `advanced-cache.php` loads BEFORE `wp-includes/pluggable.php`, so any
* `Dispatcher::config()` call made during the page-cache drop-in runs
* `load()` while `wp_salt()` is still undefined. We skip the eager
* decrypt in that window so {@see Util_Crypto::derive_key()} doesn't
* take its `SECURE_AUTH_KEY|AUTH_KEY` fallback branch — which derives a
* different key than `wp_salt('secure_auth')` and would fail to verify
* every value, collapsing each one to `''` for the rest of the request.
*
* By the time admin / settings / CDN code calls `$w3tc_config->get_string()`,
* `wp_salt()` has loaded, so we decrypt then and cache the plaintext
* back into `$_data` so subsequent reads are plain hash lookups.
*
* The runtime cache engines (Redis / Memcached page, object, db and minify
* caches) are different: they read their stored connection values during the
* same pre-pluggable window, and unlike the license/CDN consumers they have
* no later post-pluggable read to fall back on. {@see Util_Crypto::key_salt()}
* reproduces `wp_salt('secure_auth')` from the wp-config salt constants so we
* can resolve those early when the constants are present; otherwise the
* engine would receive the raw `enc:v1:...` envelope instead of the stored
* value.
*
* @since 2.10.0
*
* @param mixed $w3tc_value Value to inspect; mutated in place when an
* envelope is successfully decrypted (or collapsed to ''
* when it cannot be verified in the post-pluggable window).
*
* @return void
*/
private static function _maybe_lazy_decrypt( &$w3tc_value ) {
if ( ! is_string( $w3tc_value ) || 0 !== strncmp( $w3tc_value, 'enc:v1:', 7 ) ) {
return;
}
if ( ! class_exists( '\W3TC\Util_Crypto' ) ) {
return;
}
$wp_salt_ready = \function_exists( 'wp_salt' );
/*
* Before pluggable.php defines wp_salt(), only attempt a decrypt when
* the wp-config salt constants are available — Util_Crypto::key_salt()
* reproduces wp_salt('secure_auth') from them, so the derived key
* matches encrypt time. Without those constants we cannot derive the
* right key this early; leave the envelope intact so a later save()
* does not persist an unverified collapse over a recoverable value.
*/
if ( ! $wp_salt_ready && ! Util_Crypto::salt_constants_available() ) {
return;
}
$plain = Util_Crypto::envelope_decrypt( $w3tc_value );
if ( false !== $plain ) {
$w3tc_value = $plain;
} elseif ( $wp_salt_ready ) {
/*
* Rotated salt (or otherwise unrecoverable) in the post-pluggable
* window: collapse so the consumer treats it as needing re-entry.
* Pre-pluggable we deliberately leave the envelope in place
* (non-destructive) — see the guard above.
*/
$w3tc_value = '';
}
}
/**
* Retrieves a string configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param string $default_value The default string value to return if the key is not found.
* @param bool $trim Whether to trim the value.
*
* @return string The configuration value as a string.
*/
public function get_string( $w3tc_key, $default_value = '', $trim = true ) {
$w3tc_value = (string) $this->get( $w3tc_key, $default_value );
return $trim ? trim( $w3tc_value ) : $w3tc_value;
}
/**
* Retrieves an integer configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param int $default_value The default integer value to return if the key is not found.
*
* @return int The configuration value as an integer.
*/
public function get_integer( $w3tc_key, $default_value = 0 ) {
return (int) $this->get( $w3tc_key, $default_value );
}
/**
* Retrieves a boolean configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param bool $default_value The default boolean value to return if the key is not found.
*
* @return bool The configuration value as a boolean.
*/
public function get_boolean( $w3tc_key, $default_value = false ) {
return (bool) $this->get( $w3tc_key, $default_value );
}
/**
* Retrieves an array configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param array $default_value The default array value to return if the key is not found.
*
* @return array The configuration value as an array.
*/
public function get_array( $w3tc_key, $default_value = array() ) {
return (array) $this->get( $w3tc_key, $default_value );
}
/**
* Retrieves a filtered configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param mixed $default_value The default value to return if the key is not found.
*
* @return mixed The configuration value, potentially filtered.
*/
public function getf( $w3tc_key, $default_value = null ) {
$v = $this->get( $w3tc_key, $default_value );
return apply_filters( 'w3tc_config_item_' . $w3tc_key, $v );
}
/**
* Retrieves a filtered string configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param string $default_value The default string value to return if the key is not found.
* @param bool $trim Whether to trim the value.
*
* @return string The filtered configuration value as a string.
*/
public function getf_string( $w3tc_key, $default_value = '', $trim = true ) {
$w3tc_value = (string) $this->getf( $w3tc_key, $default_value );
return $trim ? trim( $w3tc_value ) : $w3tc_value;
}
/**
* Retrieves a filtered integer configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param int $default_value The default integer value to return if the key is not found.
*
* @return int The filtered configuration value as an integer.
*/
public function getf_integer( $w3tc_key, $default_value = 0 ) {
return (int) $this->getf( $w3tc_key, $default_value );
}
/**
* Retrieves a filtered boolean configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param bool $default_value The default boolean value to return if the key is not found.
*
* @return bool The filtered configuration value as a boolean.
*/
public function getf_boolean( $w3tc_key, $default_value = false ) {
return (bool) $this->getf( $w3tc_key, $default_value );
}
/**
* Retrieves a filtered array configuration value for a given key.
*
* @param string $w3tc_key The key to look up in the configuration.
* @param array $default_value The default array value to return if the key is not found.
*
* @return array The filtered configuration value as an array.
*/
public function getf_array( $w3tc_key, $default_value = array() ) {
return (array) $this->getf( $w3tc_key, $default_value );
}
/**
* Checks if a specific extension is active in the configuration.
*
* @param string $w3tc_extension The extension to check.
*
* @return bool True if the extension is active, false otherwise.
*/
public function is_extension_active( $w3tc_extension ) {
$extensions = $this->get_array( 'extensions.active' );
return isset( $extensions[ $w3tc_extension ] );
}
/**
* Checks if a specific extension is active on the frontend.
*
* @param string $w3tc_extension The extension to check.
*
* @return bool True if the extension is active on the frontend, false otherwise.
*/
public function is_extension_active_frontend( $w3tc_extension ) {
$extensions = $this->get_array( 'extensions.active_frontend' );
return isset( $extensions[ $w3tc_extension ] );
}
/**
* Sets the active frontend extension.
*
* @param string $w3tc_extension The extension key to be set as active.
* @param bool $is_active_frontend Whether the extension should be active on the frontend.
*
* @return void
*/
public function set_extension_active_frontend( $w3tc_extension, $is_active_frontend ) {
$w3tc_a = $this->get_array( 'extensions.active_frontend' );
if ( ! $is_active_frontend ) {
unset( $w3tc_a[ $w3tc_extension ] );
} else {
$w3tc_a[ $w3tc_extension ] = '*';
}
$this->set( 'extensions.active_frontend', $w3tc_a );
}
/**
* Sets the active dropin extension.
*
* @param string $w3tc_extension The extension key to be set as active dropin.
* @param bool $is_active_dropin Whether the extension should be active as a dropin.
*
* @return void
*/
public function set_extension_active_dropin( $w3tc_extension, $is_active_dropin ) {
$w3tc_a = $this->get_array( 'extensions.active_dropin' );
if ( ! $is_active_dropin ) {
unset( $w3tc_a[ $w3tc_extension ] );
} else {
$w3tc_a[ $w3tc_extension ] = '*';
}
$this->set( 'extensions.active_dropin', $w3tc_a );
}
/**
* Sets a key-value pair in the configuration data.
*
* @param string|array $w3tc_key The key or array of keys to set.
* @param mixed $w3tc_value The value to set.
*
* @return mixed The value that was set.
*/
public function set( $w3tc_key, $w3tc_value ) {
/**
* Strip directive-terminating bytes from values bound for keys whose
* stored string (or stored array of strings) is later concatenated
* into a `.htaccess` / `nginx.conf` directive. See
* `Util_Rule::sanitize_directive_value()` for the strip set and
* rationale. Routed through the shared helper rather than
* duplicating the regex so a future strip-set extension lands in
* one place and stored/rendered values stay in lockstep.
*
* The same characters are stripped at the renderer boundary too;
* this is the upstream defence-in-depth half so the bad bytes
* never enter master.php in the first place.
*/
if ( ! is_array( $w3tc_key ) && '' !== $w3tc_value ) {
$desc = self::directive_string_descriptor( $w3tc_key );
if ( null !== $desc ) {
if ( is_string( $w3tc_value ) ) {
$w3tc_value = Util_Rule::sanitize_directive_value( $w3tc_value );
} elseif ( is_array( $w3tc_value ) ) {
/**
* Directive-bound array keys (`pgcache.reject.cookie`,
* `pgcache.reject.ua`, `mobile.rgroups`, etc.) each
* element of which is later concatenated into a rule
* alternation. Sanitise every scalar entry and drop
* any non-scalar (the helper would return '' anyway,
* and array_filter strips the empty result so a
* fully-stripped entry doesn't widen an `implode( '|', ... )`
* regex into a match-everything alternative).
*/
$w3tc_value = array_values(
array_filter(
array_map(
function ( $v ) {
return is_scalar( $v )
? Util_Rule::sanitize_directive_value( (string) $v )
: '';
},
$w3tc_value
),
function ( $v ) {
return '' !== $v;
}
)
);
}
}
}
$w3tc_value = self::enforce_enum( $w3tc_key, $w3tc_value, $this );
if ( ! is_array( $w3tc_key ) ) {
$this->_data[ $w3tc_key ] = $w3tc_value;
} else {
// set extension's key.
$key0 = $w3tc_key[0];
$key1 = $w3tc_key[1];
if ( ! isset( $this->_data[ $key0 ] ) || ! is_array( $this->_data[ $key0 ] ) ) {
$this->_data[ $key0 ] = array();
}
$this->_data[ $key0 ][ $key1 ] = $w3tc_value;
}
return $w3tc_value;
}
/**
* Removes a configuration key from the in-memory data set.
*
* Intended for one-time migrations that retire keys whose feature
* has been removed from the plugin (e.g. a discontinued CDN engine),
* so the orphaned key — and any stored secret under it — stops being
* written back to `master.php`. The key is dropped from
* `$this->_data`; call {@see save()} afterwards to persist the
* removal. Removing a key that is not present is a no-op.
*
* Only top-level string keys are supported; compound
* `array( 'extension', 'sub' )` keys are rejected.
*
* @since 2.10.0
*
* @param string $w3tc_key Config key to remove.
*
* @return bool True if the key was present and removed; false otherwise.
*/
public function unset_key( $w3tc_key ) {
if ( ! is_string( $w3tc_key ) || ! isset( $this->_data[ $w3tc_key ] ) ) {
return false;
}
unset( $this->_data[ $w3tc_key ] );
return true;
}
/**
* Returns the descriptor for a `directive_string`-flagged key, or
* null if the key isn't in the schema or doesn't carry the flag.
*
* Loads `ConfigKeys.php` once per request and caches the slimmed
* directive-string map (keyed by config-key name → true). Kept
* local to `Config` rather than promoted to a shared schema
* accessor because the `directive_string` flag is the only piece
* of `ConfigKeys.php` that `Config::set()` consults; promoting
* would widen this file's `ConfigKeys.php` surface without a
* second consumer to justify it.
*
* @since 2.10.0
*
* @param string $w3tc_key Single-string config key.
*
* @return true|null `true` when the key is flagged; `null` otherwise.
*/
private static function directive_string_descriptor( $w3tc_key ) {
static $set = null;
if ( null === $set ) {
$set = array();
$w3tc_keys = array();
include W3TC_DIR . '/ConfigKeys.php';
if ( is_array( $w3tc_keys ) ) {
foreach ( $w3tc_keys as $w3tc_name => $w3tc_descriptor ) {
if (
is_array( $w3tc_descriptor )
&& isset( $w3tc_descriptor['flags'] )
&& is_array( $w3tc_descriptor['flags'] )
&& ! empty( $w3tc_descriptor['flags']['directive_string'] )
) {
$set[ $w3tc_name ] = true;
}
}
}
}
return isset( $set[ $w3tc_key ] ) ? true : null;
}
/**
* Constrains scalar `$w3tc_value` to the enum declared in
* {@see ConfigKeys.php} for `$w3tc_key`, when present.
*
* Schema declares an enum like:
*
* 'cdn.engine' => array(
* 'type' => 'string',
* 'default' => '',
* 'enum' => array( 'ftp', 's3', ... ),
* ),
*
* Behavior:
*
* * If the key has no `enum` entry, return `$w3tc_value` unchanged.
* * If `$w3tc_value` is in the enum, return it unchanged.
* * Otherwise, retain the value already stored under that key
* (or the schema `default`) and emit an audit-log entry. The
* invalid value never reaches `$this->_data`, so downstream
* callers — including the `header()` emitters that
* interpolate `cdn.engine` — only ever see an allowlisted
* slug.
*
* Top-level keys only (compound `array( 'extension', 'sub' )`
* keys skip enforcement); the schema doesn't currently declare
* enums on extension subkeys.
*
* @since 2.10.0
*
* @param string|array $w3tc_key Config key (string for top-level, array for extension subkey).
* @param mixed $w3tc_value The candidate value.
* @param Config $w3tc_config Config instance, used to look up the prior value as fallback.
*
* @return mixed The original value if allowed, otherwise the prior stored value (or schema default).
*/
private static function enforce_enum( $w3tc_key, $w3tc_value, Config $w3tc_config ) {
if ( ! \is_string( $w3tc_key ) ) {
return $w3tc_value;
}
$schema = self::config_keys_schema();
if ( ! isset( $schema[ $w3tc_key ]['enum'] ) || ! \is_array( $schema[ $w3tc_key ]['enum'] ) ) {
return $w3tc_value;
}
$enum = $schema[ $w3tc_key ]['enum'];
/**
* Non-scalar values are an immediate reject path. The earlier
* shape coerced them to `''` and then ran the enum match — but
* `''` is itself a valid enum member for `cdn.engine`, so an
* array / object write (e.g. from a malformed import) would
* have been *accepted* by the enum check and overwritten the
* prior value. Route non-scalars straight to the fallback
* branch below so they never normalise to an allowed slug.
*/
if ( \is_scalar( $w3tc_value ) ) {
$value_string = (string) $w3tc_value;
if ( \in_array( $value_string, $enum, true ) ) {
/**
* Return the normalised string form so the stored
* type matches the schema's declared `string` type
* even when a caller passes a non-string scalar
* (e.g. `true` → `'1'`, `42` → `'42'`) that happens
* to string-cast into an allowed enum member.
*/
return $value_string;
}
}
/**
* Reject. Retain whatever was previously stored (or fall
* back to the schema default). Audit-log the rejection so
* operators can see attempted out-of-enum writes (a CRLF-
* bearing value, or a buggy filter widening the surface).
*/
$fallback = '';
if ( isset( $w3tc_config->_data[ $w3tc_key ] ) ) {
$fallback = $w3tc_config->_data[ $w3tc_key ];
} elseif ( isset( $schema[ $w3tc_key ]['default'] ) ) {
$fallback = $schema[ $w3tc_key ]['default'];
}
/**
* Allow the autoloader to load `Util_Debug` if it isn't
* already in memory (the earlier `class_exists(..., false)`
* would skip the audit-log in any request path where
* `Util_Debug` hadn't been touched yet — i.e. most enum-write
* flows — and silently drop the diagnostic operators rely on).
*/
if ( \class_exists( __NAMESPACE__ . '\\Util_Debug' ) ) {
Util_Debug::log(
'config',
\sprintf(
'Rejected out-of-enum write to %s; retained prior value. Allowed: %s',
$w3tc_key,
\implode( ',', $enum )
)
);
}
return $fallback;
}
/**
* Lazy-load the {@see ConfigKeys.php} schema once per request.
*
* `ConfigKeys.php` populates a local `$w3tc_keys` variable; we
* import the file inside an isolated scope and cache the
* result so per-write enum lookups don't re-include the file.
*
* @since 2.10.0
*
* @return array<string, array<string, mixed>>
*/
private static function config_keys_schema() {
static $schema = null;
if ( null === $schema ) {
$schema = array();
$loader = static function () {
$w3tc_keys = array();
include W3TC_DIR . '/ConfigKeys.php';
return $w3tc_keys;
};
$schema = (array) $loader();
}
return $schema;
}
/**
* Checks if the current configuration is a preview.
*
* @return bool True if preview mode is enabled, false otherwise.
*/
public function is_preview() {
return $this->_preview;
}
/**
* Checks if the current configuration is the master configuration.
*
* @return bool True if the configuration is the master, false otherwise.
*/
public function is_master() {
return $this->_is_master;
}
/**
* Checks if the configuration is compiled.
*
* @return bool True if the configuration is compiled, false otherwise.
*/
public function is_compiled() {
return $this->_compiled;
}
/**
* Sets the default configuration values.
*
* @return void
*/
public function set_defaults() {
$w3tc_c = new ConfigCompiler( $this->_blog_id, $this->_preview );
$this->_data = $w3tc_c->get_data();
}
/**
* Saves the current configuration.
*
* @return void
*/
public function save() {
if ( function_exists( 'do_action' ) ) {
do_action( 'w3tc_config_save', $this );
}
$w3tc_c = new ConfigCompiler( $this->_blog_id, $this->_preview );
$w3tc_c->apply_data( $this->_data );
$w3tc_c->save();
}
/**
* Checks if a configuration key is sealed (immutable).
*
* @param string $w3tc_key The configuration key to check.
*
* @return bool True if the key is sealed, false otherwise.
*/
public function is_sealed( $w3tc_key ) {
if ( $this->is_master() ) {
return false;
}
// better to use master config data here, but its faster and preciese enough for UI.
return ConfigCompiler::child_key_sealed( $w3tc_key, $this->_data, $this->_data );
}
/**
* Exports the current configuration as a JSON string.
*
* @return string The configuration data as a JSON string.
*/
public function export() {
$export_data = $this->_data;
// Export files are often copied between environments; omit stored credential values.
foreach ( \array_keys( self::secret_keys() ) as $w3tc_key ) {
if ( isset( $export_data[ $w3tc_key ] ) && '' !== $export_data[ $w3tc_key ] ) {
$export_data[ $w3tc_key ] = '';
}
}
/**
* JSON_HEX_TAG / HEX_AMP / HEX_APOS / HEX_QUOT escape `<` `&` `'`
* `"` to their `\uXXXX` forms. The export endpoint serves this
* body to admins, and even with `Content-Type: application/json`
* some clients can render the response as HTML (history walks,
* view-source, intermediary proxies that ignore the content-
* type). Hex-escaping every HTML-significant character means
* the body cannot contain a literal `<script>` regardless of
* what an admin saved in config — closes that path without
* changing the JSON semantics (JSON parsers decode `<` back
* to `<` transparently).
*
* Secret-flagged keys are blanked in the export copy so
* credential material is not written into a downloadable file.
*/
$flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
if ( defined( 'JSON_PRETTY_PRINT' ) ) {
$flags |= JSON_PRETTY_PRINT;
}
return wp_json_encode( $export_data, $flags );
}
/**
* Imports a configuration from a file.
*
* Every incoming JSON key is validated against the `ConfigKeys.php`
* schema via {@see ConfigKeysSchema}. Unknown keys are dropped
* silently and high-impact keys flagged `no_import => true`
* (`extensions.active*`, `*.path.java/jar`, `*.engine`) are refused
* here even though they are documented config keys — those are
* editable only through their dedicated UI page, where the page-
* specific validator runs. Accepted values are type-coerced
* (`boolean` becomes a real `bool`, etc.) so an unexpected
* non-scalar value cannot land in a slot the schema declares
* as a scalar.
*
* Rejection counts are written to the `w3tc` debug channel via
* `Util_Debug::log` so operators can diagnose "my exported config
* didn't restore everything" without having to look at the JSON.
*
* @global $wp_filesystem
* @see get_filesystem_method()
*
* @param string $filename The path to the file to import.
*
* @return bool True if the import was successful, false otherwise.
*/
public function import( string $filename ): bool {
if ( 'direct' !== \get_filesystem_method() ) {
return false;
}
// Initialize WP_Filesystem.
global $wp_filesystem;
WP_Filesystem();
if ( $wp_filesystem->exists( $filename ) && $wp_filesystem->is_readable( $filename ) ) {
$content = $wp_filesystem->get_contents( $filename );
if ( \substr( $content, 0, 14 ) === '<?php exit; ?>' ) {
$content = \substr( $content, 14 );
}
$w3tc_data = @json_decode( $content, true );
if ( \is_array( $w3tc_data ) ) {
if ( ! isset( $w3tc_data['version'] ) || W3TC_VERSION !== $w3tc_data['version'] ) {
$w3tc_c = new ConfigCompiler( $this->_blog_id, false );
$w3tc_c->load( $w3tc_data );
$w3tc_data = $w3tc_c->get_data();
}
$rejected_unknown = 0;
$rejected_locked = 0;
$applied = 0;
foreach ( $w3tc_data as $w3tc_key => $w3tc_value ) {
// `version` is metadata, not a settable key.
if ( 'version' === $w3tc_key ) {
continue;
}
if ( ! ConfigKeysSchema::is_known( $w3tc_key ) ) {
++$rejected_unknown;
continue;
}
if ( ! ConfigKeysSchema::can_import( $w3tc_key ) ) {
++$rejected_locked;
continue;
}
$w3tc_descriptor = ConfigKeysSchema::descriptor( $w3tc_key );
$w3tc_value = ConfigKeysSchema::coerce( $w3tc_value, $w3tc_descriptor );
$this->set( $w3tc_key, $w3tc_value );
++$applied;
}
if ( ( $rejected_unknown > 0 || $rejected_locked > 0 ) && \class_exists( '\W3TC\Util_Debug' ) ) {
Util_Debug::log(
'w3tc',
\sprintf(
'Config::import: applied %d, rejected %d unknown key%s and %d no-import key%s.',
$applied,
$rejected_unknown,
1 === $rejected_unknown ? '' : 's',
$rejected_locked,
1 === $rejected_locked ? '' : 's'
)
);
}
return true;
}
}
return false;
}
/**
* Gets the MD5 hash of the current configuration data.
*
* @return string The MD5 hash of the configuration data.
*/
public function get_md5() {
if ( is_null( $this->_md5 ) ) {
$this->_md5 = substr( md5( serialize( $this->_data ) ), 20 );
}
return $this->_md5;
}
/**
* Loads the configuration data.
*
* @return void
*/
public function load() {
$w3tc_data = self::util_array_from_storage( 0, $this->_preview );
// config file assumed is not up to date, use slow version.
if ( ! isset( $w3tc_data['version'] ) || W3TC_VERSION !== $w3tc_data['version'] ) {
$this->load_full();
return;
}
if ( ! $this->is_master() ) {
$child_data = self::util_array_from_storage( $this->_blog_id, $this->_preview );
if ( ! is_null( $child_data ) ) {
if ( ! isset( $w3tc_data['version'] ) || W3TC_VERSION !== $w3tc_data['version'] ) {
$this->load_full();
return;
}
foreach ( $child_data as $w3tc_key => $w3tc_value ) {
$w3tc_data[ $w3tc_key ] = $w3tc_value;
}
}
}
self::decrypt_secrets( $w3tc_data );
$this->_data = $w3tc_data;
$this->_compiled = false;
}
/**
* Loads the full configuration data when necessary.
*
* @return void
*/
private function load_full() {
$w3tc_c = new ConfigCompiler( $this->_blog_id, $this->_preview );
$w3tc_c->load();
$w3tc_data = $w3tc_c->get_data();
self::decrypt_secrets( $w3tc_data );
$this->_data = $w3tc_data;
$this->_compiled = true;
}
/**
* Returns the set of config keys declared as `secret` in
* ConfigKeys.php — credential-typed keys whose stored value is
* encrypted with {@see Util_Crypto::envelope_encrypt()}.
*
* The schema include is cached for the request, so this is cheap
* to call from both the load and save paths.
*
* @since 2.10.0
*
* @return array<string,true> Map of secret key name → true.
*/
public static function secret_keys() {
static $cache = null;
if ( null !== $cache ) {
return $cache;
}
$w3tc_keys = array();
include W3TC_DIR . '/ConfigKeys.php';
$cache = array();
if ( is_array( $w3tc_keys ) ) {
foreach ( $w3tc_keys as $w3tc_name => $w3tc_descriptor ) {
if (
is_array( $w3tc_descriptor )
&& isset( $w3tc_descriptor['flags'] )
&& is_array( $w3tc_descriptor['flags'] )
&& ! empty( $w3tc_descriptor['flags']['secret'] )
) {
$cache[ $w3tc_name ] = true;
}
}
}
return $cache;
}
/**
* Decrypts every secret-flagged key in-place inside `$w3tc_data`.
*
* Legacy plaintext values pass through unchanged (the envelope
* helper is a no-op on non-`enc:v1:` strings), so an existing
* install upgrades transparently — values stay readable until the
* next `save()` re-wraps them.
*
* Tampered envelopes (bad HMAC, malformed base64) decrypt to
* `false`; we collapse that to an empty string so the downstream
* config consumer sees "credential needs re-entry" rather than the
* literal `false` (which `get_string()` would coerce to `""` anyway
* but via a non-obvious code path).
*
* @since 2.10.0
*
* @param array $w3tc_data Configuration data (modified in place).
*
* @return void
*/
public static function decrypt_secrets( &$w3tc_data ) {
if ( ! is_array( $w3tc_data ) || ! class_exists( '\W3TC\Util_Crypto' ) ) {
return;
}
/**
* `wp_salt()` is defined in `wp-includes/pluggable.php`, which loads
* AFTER `advanced-cache.php`. When `WP_CACHE` is defined, the W3TC
* page-cache drop-in calls `Dispatcher::config()` during early
* bootstrap — triggering `Config::__construct()` → `load()` → this
* method — well before `wp_salt()` exists. An eager decrypt here
* would force {@see Util_Crypto::derive_key()} down its
* `SECURE_AUTH_KEY|AUTH_KEY` fallback path, which produces a
* different key than `wp_salt('secure_auth')` uses at encrypt time.
* Every secret would then HMAC-fail and collapse to `''`, and the
* Dispatcher singleton would carry empty credentials for the rest
* of the request.
*
* Skip eager decrypt in that window; {@see self::_get()} lazily
* decrypts envelope values on first read instead, by which point
* `wp_salt()` is guaranteed to be loaded.
*/
if ( ! \function_exists( 'wp_salt' ) ) {
return;
}
$secret_keys = self::secret_keys();
if ( empty( $secret_keys ) ) {
return;
}
foreach ( $secret_keys as $w3tc_key => $_ ) {
if ( ! isset( $w3tc_data[ $w3tc_key ] ) || ! is_string( $w3tc_data[ $w3tc_key ] ) ) {
continue;
}
$plain = Util_Crypto::envelope_decrypt( $w3tc_data[ $w3tc_key ] );
if ( false === $plain ) {
$w3tc_data[ $w3tc_key ] = '';
} else {
$w3tc_data[ $w3tc_key ] = $plain;
}
}
}
/**
* Encrypts every secret-flagged key in-place inside `$w3tc_data`.
*
* Called by {@see ConfigCompiler::save()} before writing
* `master.php`. Non-secret keys are untouched. Already-enveloped
* values are not double-wrapped (the envelope helper detects its
* own prefix and short-circuits).
*
* @since 2.10.0
*
* @param array $w3tc_data Configuration data (modified in place).
*
* @return void
*/
public static function encrypt_secrets( &$w3tc_data ) {
if ( ! is_array( $w3tc_data ) || ! class_exists( '\W3TC\Util_Crypto' ) ) {
return;
}
/*
* Mirror the guard in {@see self::decrypt_secrets()}: the envelope key
* is derived from `wp_salt('secure_auth')`, and `wp_salt()` is the only
* authoritative source of that key. It cannot be reliably reproduced
* from the wp-config salt constants: WordPress ignores any key/salt
* value that is duplicated across schemes (the `$duplicated_keys`
* logic in `wp_salt()`), honours a `salt` filter, and generates and
* stores its own random salts in the database when the constants are
* absent or duplicated. In every one of those cases
* `SECURE_AUTH_KEY . SECURE_AUTH_SALT` differs from what `wp_salt()`
* returns.
*
* Encrypting before `wp_salt()` is defined (early bootstrap — the
* page-cache / object-cache drop-ins, or a plugin's `run()` that saves
* config on the `plugins_loaded` side of `wp-includes/pluggable.php`)
* therefore risks keying the envelope with a salt that will not match
* at decrypt time, silently collapsing every secret to '' on the next
* admin read.
*
* Skip encryption in that window: the value is persisted in whatever
* form the in-memory config already holds — a legacy plaintext value
* stays plaintext (matching the pre-2.10.0 behaviour and the
* {@see Util_Crypto::is_available()} fallback), while an already-wrapped
* value stays wrapped, because {@see self::decrypt_secrets()} and
* {@see self::_maybe_lazy_decrypt()} leave envelopes intact pre-pluggable.
* It is (re-)wrapped by the first save that runs with `wp_salt()`
* available.
*/
if ( ! \function_exists( 'wp_salt' ) ) {
return;
}
$secret_keys = self::secret_keys();
if ( empty( $secret_keys ) ) {
return;
}
foreach ( $secret_keys as $w3tc_key => $_ ) {
if ( ! isset( $w3tc_data[ $w3tc_key ] ) || ! is_string( $w3tc_data[ $w3tc_key ] ) || '' === $w3tc_data[ $w3tc_key ] ) {
continue;
}
$w3tc_data[ $w3tc_key ] = Util_Crypto::envelope_encrypt( $w3tc_data[ $w3tc_key ] );
}
}
}