`) so concurrent rewrite
* tests cannot clobber each other's tokens. Only requests presenting a
* matching token via the {@see self::PROBE_TOKEN_HEADER} header may
* trigger the `OK` response.
*
* @since 2.10.0
*
* @var string
*/
const PROBE_TOKEN_PREFIX = 'w3tc_pgcache_probe_';
/**
* HTTP header carrying the probe token from the issuing admin
* request to the unauthenticated `init`-hook rewrite-test handler.
*
* @since 2.10.0
*
* @var string
*/
const PROBE_TOKEN_HEADER = 'X-W3TC-PgCache-Probe';
/**
* Fixes environment settings on WP admin request.
*
* @param Config $w3tc_config Configuration object containing environment settings.
* @param bool $force_all_checks Whether to force all checks regardless of current configuration.
*
* @return void
*
* @throws \Util_Environment_Exceptions If there are unresolved exceptions during the fixing process.
*/
public function fix_on_wpadmin_request( $w3tc_config, $force_all_checks ) {
$exs = new Util_Environment_Exceptions();
$pgcache_enabled = $w3tc_config->get_boolean( 'pgcache.enabled' );
$w3tc_engine = $w3tc_config->get_string( 'pgcache.engine' );
// WordPress defines WP_CACHE as false in wp_initial_constants() when the
// site did not. A true value was set by the site, so leave wp-config alone.
// A false value still has to be checked against the site's config files.
if ( ! defined( 'WP_CACHE' ) || ! WP_CACHE ) {
try {
$this->wp_config_add_directive();
} catch ( Util_WpFile_FilesystemOperationException $ex ) {
$exs->push( $ex );
}
}
$this->fix_folders( $w3tc_config, $exs );
if ( $w3tc_config->get_boolean( 'config.check' ) || $force_all_checks ) {
$this->rules_apply_for_config( $w3tc_config, $exs );
}
// if no errors so far - check if rewrite actually works.
if ( count( $exs->exceptions() ) <= 0 ) {
try {
if ( $pgcache_enabled && 'file_generic' === $w3tc_engine ) {
$this->verify_file_generic_compatibility();
// The rewrite test only makes sense while W3TC owns the rules: is_rules_required()
// answers no once something else took delivery.
if ( $w3tc_config->get_boolean( 'pgcache.debug' ) && $this->is_rules_required( $w3tc_config ) ) {
$this->verify_file_generic_rewrite_working();
}
}
} catch ( \Exception $ex ) {
$exs->push( $ex );
}
}
if ( count( $exs->exceptions() ) > 0 ) {
throw $exs;
}
}
/**
* Adjusts settings based on a specific event.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param string $event The name of the event triggering the adjustment.
* @param Config|null $old_config Optional. Previous W3TC Config object for comparison.
*
* @return void
*
* @throws Util_Environment_Exceptions When rewrite rules cannot be updated on config save.
*/
public function fix_on_event( $w3tc_config, $event, $old_config = null ) {
$pgcache_enabled = $w3tc_config->get_boolean( 'pgcache.enabled' );
$w3tc_engine = $w3tc_config->get_string( 'pgcache.engine' );
// Schedules events.
if ( $pgcache_enabled && ( 'file' === $w3tc_engine || 'file_generic' === $w3tc_engine ) ) {
$new_interval = $w3tc_config->get_integer( 'pgcache.file.gc' );
$old_interval = $old_config ? $old_config->get_integer( 'pgcache.file.gc' ) : -1;
if ( null !== $old_config && $new_interval !== $old_interval ) {
$this->unschedule_gc();
}
if ( ! wp_next_scheduled( 'w3_pgcache_cleanup' ) ) {
wp_schedule_event( time(), 'w3_pgcache_cleanup', 'w3_pgcache_cleanup' );
}
} else {
$this->unschedule_gc();
}
// Schedule prime event.
$prime_enabled = $pgcache_enabled && $w3tc_config->get_boolean( 'pgcache.prime.enabled' );
$one_pass = $w3tc_config->get_boolean( 'pgcache.prime.sitemap_one_pass' );
$restart_prime = false;
$signature = $this->prime_settings_signature( $w3tc_config );
if ( null !== $old_config ) {
$old_prime_enabled = (
$old_config->get_boolean( 'pgcache.enabled' ) &&
$old_config->get_boolean( 'pgcache.prime.enabled' )
);
$restart_prime = $prime_enabled && (
! $old_prime_enabled ||
$one_pass !== $old_config->get_boolean( 'pgcache.prime.sitemap_one_pass' ) ||
$w3tc_config->get_string( 'pgcache.prime.sitemap' ) !==
$old_config->get_string( 'pgcache.prime.sitemap' )
);
} else {
$stored_signature = get_option( PgCache_Plugin_Admin::PRIME_SETTINGS_OPTION, false );
$restart_prime = (
$prime_enabled &&
false !== $stored_signature &&
! hash_equals( (string) $stored_signature, $signature )
);
}
if ( 'activate' === $event && $prime_enabled ) {
$restart_prime = true;
}
if ( ! $prime_enabled || $restart_prime ) {
PgCache_Plugin_Admin::reset_prime();
}
update_option( PgCache_Plugin_Admin::PRIME_SETTINGS_OPTION, $signature, false );
$prime_completed = (
$one_pass &&
PgCache_Plugin_Admin::prime_generation() ===
get_option( PgCache_Plugin_Admin::PRIME_COMPLETED_OPTION, false )
);
if ( $prime_enabled && ! $prime_completed ) {
$new_interval = $w3tc_config->get_integer( 'pgcache.prime.interval' );
$old_interval = $old_config ? $old_config->get_integer( 'pgcache.prime.interval' ) : -1;
if ( $restart_prime || ( null !== $old_config && $new_interval !== $old_interval ) ) {
$this->unschedule_prime();
}
if ( ! wp_next_scheduled( 'w3_pgcache_prime' ) ) {
wp_schedule_event( time(), 'w3_pgcache_prime', 'w3_pgcache_prime' );
}
} else {
$this->unschedule_prime();
}
if ( 'config_change' === $event ) {
$exs = new Util_Environment_Exceptions();
$this->fix_folders( $w3tc_config, $exs );
$this->rules_apply_for_config( $w3tc_config, $exs );
if ( count( $exs->exceptions() ) > 0 ) {
throw $exs;
}
}
}
/**
* Creates the per-site signature for effective preload settings.
*
* @since 2.10.7
*
* @param Config $w3tc_config W3TC configuration.
*
* @return string
*/
private function prime_settings_signature( $w3tc_config ) {
return hash(
'sha256',
wp_json_encode(
array(
'enabled' => (
$w3tc_config->get_boolean( 'pgcache.enabled' ) &&
$w3tc_config->get_boolean( 'pgcache.prime.enabled' )
),
'one_pass' => $w3tc_config->get_boolean( 'pgcache.prime.sitemap_one_pass' ),
'sitemap' => $w3tc_config->get_string( 'pgcache.prime.sitemap' ),
)
)
);
}
/**
* Adds or removes page-cache rewrite rules, for this plugin and for an extension that took delivery over.
*
* @since 2.10.0
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param Util_Environment_Exceptions $exs Exception collection.
*
* @return void
*/
public function rules_apply_for_config( $w3tc_config, $exs ) {
if ( $this->is_rules_required( $w3tc_config ) ) {
$this->rules_core_add( $w3tc_config, $exs );
$this->rules_cache_add( $w3tc_config, $exs );
} else {
$this->rules_core_remove( $exs );
$this->rules_cache_remove( $exs );
}
}
/**
* Fixes the environment after plugin deactivation.
*
* @return void
*
* @throws \Util_Environment_Exceptions If exceptions occur during cleanup.
*/
public function fix_after_deactivation() {
$exs = new Util_Environment_Exceptions();
try {
$this->wp_config_remove_directive( $exs );
} catch ( Util_WpFile_FilesystemOperationException $ex ) {
$exs->push( $ex );
}
$this->rules_core_remove( $exs );
$this->rules_cache_remove( $exs );
$this->unschedule_gc();
$this->unschedule_prime();
$this->unschedule_purge_wpcron();
PgCache_Plugin_Admin::reset_prime();
if ( count( $exs->exceptions() ) > 0 ) {
throw $exs;
}
}
/**
* Returns a copy of `$values` with every element passed through
* {@see Util_Rule::sanitize_directive_value()}.
*
* Used by the rule-emitting paths above so admin-set arrays
* (mobile/referrer agents, reject-cookie lists, rgroup keys)
* can't carry a CR/LF / NUL / `<` / `>` / `"` into the rendered
* `.htaccess` / `nginx.conf` and start a fresh directive on the
* next line.
*
* Non-string entries pass through the helper unchanged (it returns
* `''` for any non-string input). Entries that sanitise to an
* empty string are dropped before return so a downstream
* `implode( '|', ... )` regex alternation cannot pick up an empty
* alternative that matches every request.
*
* @since 2.10.0
*
* @param array $values Raw array of values.
*
* @return array Sanitised values, in input order, with empties removed.
*/
private static function sanitize_directive_values( $values ) {
if ( ! is_array( $values ) ) {
return array();
}
$out = array();
foreach ( $values as $v ) {
$sanitised = Util_Rule::sanitize_directive_value( $v );
if ( '' !== $sanitised ) {
$out[] = $sanitised;
}
}
return $out;
}
/**
* Determines if rules are required for the configuration.
*
* @since 2.0.0
*
* @param Config $w3tc_c W3TC Config containing relevant settings.
*
* @return bool True if rules are required, false otherwise.
*/
private function is_rules_required( $w3tc_c ) {
$e = $w3tc_c->get_string( 'pgcache.engine' );
$required = $w3tc_c->get_boolean( 'pgcache.enabled' ) && ( 'file_generic' === $e || 'nginx_memcached' === $e );
/**
* Filter: Allow external code to suppress the page-cache rewrite rules.
*
* Returning false stops W3TC from writing its page-cache rewrite rules and removes the
* core rules it wrote earlier, on both the Apache and the nginx path. Cache generation
* is untouched, so pages keep being cached and delivery can be handled elsewhere - by a
* server module, for example.
*
* The filter can only suppress: it is not consulted for configurations that do not use
* rewrite rules in the first place, so it cannot emit rules for them.
*
* @since 2.10.6
*
* @param bool $required Whether page-cache rewrite rules are required. Always true here,
* since the filter runs only when W3TC would write the rules.
* @param Config $w3tc_c W3TC Config containing relevant settings.
*/
return $required && (bool) \apply_filters( 'w3tc_pgcache_rules_required', $required, $w3tc_c );
}
/**
* Retrieves the required rules for the environment.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
*
* @return array|null Array of required rules or null if none are required.
*/
public function get_required_rules( $w3tc_config ) {
if ( ! $this->is_rules_required( $w3tc_config ) ) {
return null;
}
$rewrite_rules = array();
$pgcache_rules_core_path = Util_Rule::get_pgcache_rules_core_path();
$rewrite_rules[] = array(
'filename' => $pgcache_rules_core_path,
'content' => $this->rules_core_generate( $w3tc_config ),
'priority' => 1000,
);
$pgcache_rules_cache_path = Util_Rule::get_pgcache_rules_cache_path();
$rewrite_rules[] = array(
'filename' => $pgcache_rules_cache_path,
'content' => $this->rules_cache_generate( $w3tc_config ),
);
return $rewrite_rules;
}
/**
* Ensures folders meet the required settings for caching.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param object $exs Exception handler object.
*
* @return void
*/
private function fix_folders( $w3tc_config, $exs ) {
if ( ! $w3tc_config->get_boolean( 'pgcache.enabled' ) ) {
return;
}
// folder that we delete if exists and not writeable.
if ( 'file_generic' === $w3tc_config->get_string( 'pgcache.engine' ) ) {
$dir = W3TC_CACHE_PAGE_ENHANCED_DIR;
} elseif ( 'file' !== $w3tc_config->get_string( 'pgcache.engine' ) ) {
$dir = W3TC_CACHE_DIR . '/page';
} else {
return;
}
try {
if ( file_exists( $dir ) && ! is_writeable( $dir ) ) {
Util_WpFile::delete_folder( $dir, '', isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '' );
}
if ( 'file_generic' === $w3tc_config->get_string( 'pgcache.engine' ) ) {
Util_WpFile::create_writeable_folder( $dir, dirname( W3TC_CACHE_DIR ) );
}
} catch ( Util_WpFile_FilesystemRmdirException $ex ) {
$exs->push( $ex );
} catch ( Util_WpFile_FilesystemOperationException $ex ) {
$exs->push( $ex );
}
}
/**
* Verifies compatibility for file-based generic caching.
*
* @return void
*
* @throws \Util_Environment_Exception If the permalink structure is incompatible.
*/
private function verify_file_generic_compatibility() {
$w3tc_permalink_structure = get_option( 'permalink_structure' );
if ( empty( $w3tc_permalink_structure ) ) {
throw new Util_Environment_Exception( 'Disk Enhanced mode can\'t work with "Default" permalinks structure' );
}
}
/**
* Validates the functionality of file-based rewrite rules.
*
* @return void
*
* @throws \Util_Environment_Exception If rewrite rules are not functioning as expected.
*/
private function verify_file_generic_rewrite_working() {
$w3tc_url = get_home_url() . '/w3tc_rewrite_test' . wp_rand();
if ( ! $this->test_rewrite( $w3tc_url ) ) {
$w3tc_key = sprintf( 'w3tc_rewrite_test_result_%s', substr( md5( $w3tc_url ), 0, 16 ) );
$w3tc_result = get_transient( $w3tc_key );
$home_url = get_home_url();
$tech_message =
( Util_Environment::is_nginx() ? 'nginx configuration file' : '.htaccess file' ) .
' contains rules to rewrite url ' . $home_url . '/w3tc_rewrite_test into ' .
$home_url . '/?w3tc_rewrite_test which, if handled by plugin, return "OK" message.
' .
'The plugin made a request to ' . $home_url . '/w3tc_rewrite_test but received:
' .
$w3tc_result . '
instead of "OK" response.
';
$error = 'W3 Total Cache error: It appears Page Cache ' .
'URL rewriting is not working. ';
if ( Util_Environment::is_preview_mode() ) {
$error .= ' This could be due to using Preview mode. Click here to manually verify its working. It should say OK.
';
}
if ( Util_Environment::is_nginx() ) {
$error .= 'Please verify that all configuration files are ' .
'included in the configuration file (and that you have reloaded / restarted nginx).';
} else {
$error .= 'Please verify that the server configuration allows .htaccess';
}
$error .= '
Unfortunately disk enhanced page caching will ' .
'not function without custom rewrite rules. ' .
'Please ask your server administrator for assistance. Also refer to the install page for the rules for your server.';
throw new Util_Environment_Exception(
esc_html( $error ),
esc_html( $tech_message )
);
}
}
/**
* Tests the rewrite rule functionality.
*
* @param string $w3tc_url URL to test.
*
* @return bool True if the rewrite works, false otherwise.
*/
private function test_rewrite( $w3tc_url ) {
$w3tc_key = sprintf( 'w3tc_rewrite_test_%s', substr( md5( $w3tc_url ), 0, 16 ) );
$w3tc_result = get_transient( $w3tc_key );
if ( false === $w3tc_result ) {
/**
* The rewrite-test probe URL points at the unauthenticated
* `Generic_Plugin::on_init` handler. Anonymous callers must
* not be able to drive the `?w3tc_rewrite_test=1` echo, so we
* issue a one-shot capability token and forward it via the
* PROBE_TOKEN_HEADER. The handler only emits `OK` when the
* token matches.
*/
$probe_token = Util_ProbeToken::issue( self::PROBE_TOKEN_PREFIX );
$response = Util_Http::get(
$w3tc_url,
array(
'headers' => array(
self::PROBE_TOKEN_HEADER => $probe_token,
),
)
);
$w3tc_result = (
! is_wp_error( $response ) &&
200 === $response['response']['code'] &&
'OK' === trim( $response['body'] )
);
if ( $w3tc_result ) {
set_transient( $w3tc_key, $w3tc_result, 30 );
} else {
$key_result = sprintf( 'w3tc_rewrite_test_result_%s', substr( md5( $w3tc_url ), 0, 16 ) );
set_transient(
$key_result,
is_wp_error( $response ) ? $response->get_error_message() : implode( ' ', $response['response'] ),
30
);
}
}
return $w3tc_result;
}
/**
* Unschedules the garbage collection task.
*
* @return void
*/
private function unschedule_gc() {
if ( wp_next_scheduled( 'w3_pgcache_cleanup' ) ) {
wp_clear_scheduled_hook( 'w3_pgcache_cleanup' );
}
}
/**
* Unschedules the page cache priming task.
*
* @return void
*/
private function unschedule_prime() {
if ( wp_next_scheduled( 'w3_pgcache_prime' ) ) {
wp_clear_scheduled_hook( 'w3_pgcache_prime' );
}
}
/**
* Unschedules the purge WP Cron task.
*
* @since 2.8.0
*
* @return void
*/
private function unschedule_purge_wpcron() {
if ( wp_next_scheduled( 'w3tc_pgcache_purge_wpcron' ) ) {
wp_clear_scheduled_hook( 'w3tc_pgcache_purge_wpcron' );
}
}
/**
* Build wp-config contents for the WP_CACHE constant.
*
* The PHP constant is not a reliable signal here: WordPress defines it as
* false before admin code runs. Skip the write when wp-config.php, or a
* project file it loads, already defines WP_CACHE (true or false). Otherwise
* insert the W3TC snippet once.
*
* @since 2.11.0
*
* @param string $config_path Path to wp-config.php.
* @param string $config_data Current wp-config.php contents.
* @return string
*/
private function wp_config_content_for_cache_constant( $config_path, $config_data ) {
if ( $this->site_config_defines_wp_cache( $config_path, $config_data ) ) {
return $config_data;
}
$new_config_data = $this->wp_config_remove_from_content( $config_data );
$updated = preg_replace(
'~<\?(php)?~',
"\\0\r\n" . $this->wp_config_addon(),
$new_config_data,
1
);
return is_string( $updated ) ? $updated : $config_data;
}
/**
* Whether the site's config, not WordPress core, already defines WP_CACHE.
*
* @since 2.11.0
*
* @param string $config_path Path to the file being checked.
* @param string $config_data Contents of that file.
* @param int $depth Include depth. wp-config is 0.
* @return bool
*/
private function site_config_defines_wp_cache( $config_path, $config_data, $depth = 0 ) {
$code = $this->strip_php_comments( $config_data );
if ( $this->content_defines_wp_cache( $code ) ) {
return true;
}
if ( $this->environment_configs_define_wp_cache( $config_path, $code ) ) {
return true;
}
if ( $depth > 2 ) {
return false;
}
foreach ( $this->config_include_expressions( $code ) as $expression ) {
$included = $this->resolve_site_config_include( $config_path, $expression );
if ( ! is_string( $included ) ) {
continue;
}
$included_data = @file_get_contents( $included );
if ( ! is_string( $included_data ) ) {
continue;
}
if ( $this->site_config_defines_wp_cache( $included, $included_data, $depth + 1 ) ) {
return true;
}
}
return false;
}
/**
* Whether a config file defines WP_CACHE, including Roots Config::define().
*
* @since 2.11.0
*
* @param string $config_data File contents.
* @return bool
*/
private function content_defines_wp_cache( $config_data ) {
return (bool) preg_match(
"~define\\s*\\(\\s*['\"]WP_CACHE['\"]~i",
$this->strip_php_comments( $config_data )
);
}
/**
* Remove PHP comments so commented defines are not treated as live code.
*
* @since 2.11.0
*
* @param string $config_data File contents.
* @return string
*/
private function strip_php_comments( $config_data ) {
$tokens = token_get_all( $config_data );
$code = '';
foreach ( $tokens as $token ) {
if ( is_array( $token ) && in_array( $token[0], array( T_COMMENT, T_DOC_COMMENT ), true ) ) {
continue;
}
$code .= is_array( $token ) ? $token[1] : $token;
}
return $code;
}
/**
* Read PHP expressions that follow require/include in a config file.
*
* @since 2.11.0
*
* @param string $config_data File contents.
* @return string[]
*/
private function config_include_expressions( $config_data ) {
$expressions = array();
if ( ! preg_match_all( '~(?:require|include)(?:_once)?\s*~i', $config_data, $matches, PREG_OFFSET_CAPTURE ) ) {
return $expressions;
}
foreach ( $matches[0] as $match ) {
$expression = $this->read_php_expression( $config_data, $match[1] + strlen( $match[0] ) );
if ( '' !== $expression ) {
$expressions[] = $expression;
}
}
return $expressions;
}
/**
* Read one PHP expression, stopping at the statement semicolon.
*
* @since 2.11.0
*
* @param string $config_data File contents.
* @param int $start Offset of the expression.
* @return string
*/
private function read_php_expression( $config_data, $start ) {
$length = strlen( $config_data );
$depth = 0;
$quote = '';
$expression = '';
for ( $i = $start; $i < $length; $i++ ) {
$char = $config_data[ $i ];
if ( '' !== $quote ) {
$expression .= $char;
if ( '\\' === $char && ( $i + 1 ) < $length ) {
$expression .= $config_data[ ++$i ];
} elseif ( $char === $quote ) {
$quote = '';
}
continue;
}
if ( "'" === $char || '"' === $char ) {
$quote = $char;
$expression .= $char;
continue;
}
if ( '(' === $char ) {
++$depth;
$expression .= $char;
continue;
}
if ( ')' === $char ) {
if ( 0 === $depth ) {
break;
}
--$depth;
$expression .= $char;
continue;
}
if ( ';' === $char && 0 === $depth ) {
break;
}
$expression .= $char;
}
return trim( $expression );
}
/**
* Resolve a static project include. Core and vendor files are ignored.
*
* @since 2.11.0
*
* @param string $config_path File that contains the include.
* @param string $expression PHP expression after require/include.
* @return string|null Absolute path, or null when the include is not a site config.
*/
private function resolve_site_config_include( $config_path, $expression ) {
$expression = $this->strip_wrapping_parentheses( trim( $expression ) );
if (
'' === $expression ||
false !== strpos( $expression, 'ABSPATH' ) ||
false !== strpos( $expression, '$' )
) {
return null;
}
$expression = $this->normalize_file_dirname( $expression );
if ( preg_match( "~^dirname\\s*\\(\\s*__DIR__\\s*\\)\\s*\\.\\s*(['\"])([^'\"]+)\\1$~", $expression, $matches ) ) {
$base = dirname( $config_path );
$relative = $matches[2];
$candidate = dirname( $base ) . '/' . ltrim( $relative, '/' );
} elseif ( preg_match( "~^__DIR__\\s*\\.\\s*(['\"])([^'\"]+)\\1$~", $expression, $matches ) ) {
$relative = $matches[2];
$candidate = dirname( $config_path ) . '/' . ltrim( $relative, '/' );
} elseif ( preg_match( "~^(['\"])([^'\"]+)\\1$~", $expression, $matches ) ) {
$candidate = $matches[2];
if ( ! $this->path_is_absolute( $candidate ) ) {
$candidate = dirname( $config_path ) . '/' . $candidate;
}
} else {
return null;
}
if ( false !== strpos( $candidate, '/vendor/' ) || false !== strpos( $candidate, '\\vendor\\' ) ) {
return null;
}
$real = realpath( $candidate );
if ( false === $real || ! is_file( $real ) || filesize( $real ) > 1048576 ) {
return null;
}
if ( 'wp-settings.php' === basename( $real ) ) {
return null;
}
$real_config = realpath( $config_path );
$config_dir = $real_config ? dirname( $real_config ) : dirname( $config_path );
$allowed = array( $config_dir, dirname( $config_dir ) );
foreach ( $allowed as $root ) {
if ( $real === $root || 0 === strpos( $real, $root . DIRECTORY_SEPARATOR ) ) {
if ( false !== strpos( $real, DIRECTORY_SEPARATOR . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
return null;
}
if ( false !== strpos( $real, DIRECTORY_SEPARATOR . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
return null;
}
return $real;
}
}
return null;
}
/**
* Whether the active environment file next to this config defines WP_CACHE.
*
* Bedrock loads config/environments/{WP_ENV}.php through a variable, so the
* require itself cannot be resolved. Only that active file counts. A define
* in another environment must not block the snippet.
*
* @since 2.11.0
*
* @param string $config_path File that mentioned the environments directory.
* @param string $config_data Contents of that file.
* @return bool
*/
private function environment_configs_define_wp_cache( $config_path, $config_data ) {
$config_data = $this->strip_php_comments( $config_data );
if ( false === strpos( $config_data, '/environments/' ) && false === strpos( $config_data, '\\environments\\' ) ) {
return false;
}
$environment = $this->active_wp_env();
if ( ! is_string( $environment ) || 1 !== preg_match( '/^[A-Za-z0-9_-]+$/', $environment ) ) {
return false;
}
$file = dirname( $config_path ) . '/environments/' . $environment . '.php';
if ( ! is_file( $file ) ) {
return false;
}
$contents = @file_get_contents( $file );
return is_string( $contents ) && $this->content_defines_wp_cache( $contents );
}
/**
* Active environment name from WP_ENV, when the site defined one.
*
* @since 2.11.0
*
* @return string
*/
protected function active_wp_env() {
if ( ! defined( 'WP_ENV' ) || ! is_string( WP_ENV ) ) {
return '';
}
return WP_ENV;
}
/**
* Treat dirname(__FILE__) as __DIR__ so static includes resolve.
*
* Calls to dirname(__FILE__) are rewritten to __DIR__. A nested call becomes
* dirname(__DIR__) after the inner call is replaced. Whitespace inside the
* parentheses is ignored. The two-argument forms dirname(__FILE__, 1) and
* dirname(__FILE__, 2) are the same directories.
*
* A require whose whole expression is one quoted string is left unchanged.
*
* @since 2.11.0
*
* @param string $expression PHP expression after require/include.
* @return string
*/
private function normalize_file_dirname( $expression ) {
if ( preg_match( "~^(['\"])(?:\\\\.|(?!\\1).)*\\1$~s", $expression ) ) {
return $expression;
}
$expression = preg_replace( '~dirname\s*\(\s*__FILE__\s*,\s*2\s*\)~i', 'dirname(__DIR__)', $expression );
$expression = preg_replace( '~dirname\s*\(\s*__FILE__\s*,\s*1\s*\)~i', '__DIR__', $expression );
$expression = preg_replace( '~dirname\s*\(\s*__FILE__\s*\)~i', '__DIR__', $expression );
return is_string( $expression ) ? $expression : '';
}
/**
* Remove parentheses that wrap a whole require expression.
*
* @since 2.11.0
*
* @param string $expression PHP expression after require/include.
* @return string
*/
private function strip_wrapping_parentheses( $expression ) {
$expression = trim( $expression );
for ( $unwraps = 0; $unwraps < 3; $unwraps++ ) {
$inner = $this->unwrap_one_parenthesis( $expression );
if ( null === $inner ) {
break;
}
$expression = trim( $inner );
}
return $expression;
}
/**
* Return the inside of one wrapping parenthesis pair, when it wraps everything.
*
* @since 2.11.0
*
* @param string $expression Expression that may start with '('.
* @return string|null Inside text, or null when the first parenthesis does not wrap the expression.
*/
private function unwrap_one_parenthesis( $expression ) {
$length = strlen( $expression );
if ( $length < 2 || '(' !== $expression[0] ) {
return null;
}
$depth = 0;
$quote = '';
for ( $i = 0; $i < $length; $i++ ) {
$char = $expression[ $i ];
if ( '' !== $quote ) {
if ( '\\' === $char && ( $i + 1 ) < $length ) {
++$i;
continue;
}
if ( $char === $quote ) {
$quote = '';
}
continue;
}
if ( "'" === $char || '"' === $char ) {
$quote = $char;
continue;
}
if ( '(' === $char ) {
++$depth;
continue;
}
if ( ')' !== $char ) {
continue;
}
--$depth;
if ( 0 !== $depth ) {
continue;
}
if ( '' !== trim( substr( $expression, $i + 1 ) ) ) {
return null;
}
return substr( $expression, 1, $i - 1 );
}
return null;
}
/**
* Whether a path is absolute.
*
* @since 2.11.0
*
* @param string $path Path to test.
* @return bool
*/
private function path_is_absolute( $path ) {
if ( '' === $path ) {
return false;
}
if ( '/' === $path[0] || '\\' === $path[0] ) {
return true;
}
return 1 === preg_match( '~^[A-Za-z]:[\\\\/]~', $path );
}
/**
* Adds required directives to the wp-config.php file.
*
* @return void
*
* @throws \Util_WpFile_FilesystemModifyException If modifications to the file fail.
*
* phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
*/
private function wp_config_add_directive() {
$config_path = Util_Environment::wp_config_path();
if ( ! is_string( $config_path ) || '' === $config_path ) {
return;
}
$config_data = @file_get_contents( $config_path );
if ( false === $config_data ) {
return;
}
$new_config_data = $this->wp_config_content_for_cache_constant( $config_path, $config_data );
if ( $new_config_data !== $config_data ) {
try {
Util_WpFile::write_to_file( $config_path, $new_config_data );
} catch ( Util_WpFile_FilesystemOperationException $ex ) {
throw new Util_WpFile_FilesystemModifyException(
$ex->getMessage(),
$ex->credentials_form(),
'Edit file ' . $config_path . ' and add next lines:',
$config_path,
$this->wp_config_addon()
);
}
/**
* That file was in opcache for sure and it may take time to
* start execution of new modified now version.
*/
$w3tc_o = Dispatcher::component( 'SystemOpCache_Core' );
$w3tc_o->flush();
}
}
/**
* Removes added directives from the wp-config.php file.
*
* @return void
*
* @throws \Util_WpFile_FilesystemModifyException If modifications to the file fail.
*/
private function wp_config_remove_directive() {
$config_path = Util_Environment::wp_config_path();
if ( ! is_string( $config_path ) || '' === $config_path ) {
return;
}
$config_data = @file_get_contents( $config_path );
if ( false === $config_data ) {
return;
}
$new_config_data = $this->wp_config_remove_from_content( $config_data );
if ( $new_config_data !== $config_data ) {
try {
Util_WpFile::write_to_file( $config_path, $new_config_data );
} catch ( Util_WpFile_FilesystemOperationException $ex ) {
throw new Util_WpFile_FilesystemModifyException(
$ex->getMessage(),
$ex->credentials_form(),
'Edit file ' . $config_path . ' and remove next lines:',
$config_path,
$this->wp_config_addon()
);
}
}
}
/**
* Generates the addon directive content for wp-config.php.
*
* @return string The directive content.
*/
private function wp_config_addon() {
return "/** Enable W3 Total Cache */\r\ndefine('WP_CACHE', true); // Added by W3 Total Cache\r\n";
}
/**
* Removes W3TC directives from wp-config.php content.
*
* @param string $config_data The content of wp-config.php.
*
* @return string Modified content without W3TC directives.
*/
private function wp_config_remove_from_content( $config_data ) {
$config_data = preg_replace(
"~\\/\\*\\* Enable W3 Total Cache \\*\\*?\\/.*?\\/\\/ Added by W3 Total Cache(\r\n)*~s",
'',
$config_data
);
$config_data = preg_replace(
"~(\\/\\/\\s*)?define\\s*\\(\\s*['\"]?WP_CACHE['\"]?\\s*,.*?\\)\\s*;+\\r?\\n?~is",
'',
$config_data
);
return $config_data;
}
/**
* Adds core rules to the configuration.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param object $exs Exception handler object.
*
* @return void
*/
private function rules_core_add( $w3tc_config, $exs ) {
Util_Rule::add_rules(
$exs,
Util_Rule::get_pgcache_rules_core_path(),
$this->rules_core_generate( $w3tc_config ),
W3TC_MARKER_BEGIN_PGCACHE_CORE,
W3TC_MARKER_END_PGCACHE_CORE,
array(
W3TC_MARKER_BEGIN_WORDPRESS => 0,
W3TC_MARKER_END_MINIFY_CORE => strlen( W3TC_MARKER_END_MINIFY_CORE ) + 1,
W3TC_MARKER_END_BROWSERCACHE_CACHE => strlen( W3TC_MARKER_END_BROWSERCACHE_CACHE ) + 1,
W3TC_MARKER_END_PGCACHE_CACHE => strlen( W3TC_MARKER_END_PGCACHE_CACHE ) + 1,
W3TC_MARKER_END_MINIFY_CACHE => strlen( W3TC_MARKER_END_MINIFY_CACHE ) + 1,
),
true
);
}
/**
* Removes core rules from the configuration.
*
* @param object $exs Exception handler object.
*
* @return void
*/
private function rules_core_remove( $exs ) {
Util_Rule::remove_rules(
$exs,
Util_Rule::get_pgcache_rules_core_path(),
W3TC_MARKER_BEGIN_PGCACHE_CORE,
W3TC_MARKER_END_PGCACHE_CORE
);
}
/**
* Returns every cookie name whose presence keeps a visitor out of the page cache.
*
* @since 2.11.0
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param bool $logged_out Whether the just-logged-out cookie belongs in the list. Passed at
* every call site rather than defaulted: it is the one place the two rule generators differ.
* @return string[]
*/
public static function reject_cookies( $w3tc_config, $logged_out ) {
// Auto reject cookies.
$reject_cookies = array(
'comment_author',
'wp-postpass',
);
if ( $logged_out ) {
$reject_cookies[] = 'w3tc_logged_out';
}
// Reject cache for logged in users OR Reject cache for roles if any.
if ( $w3tc_config->get_boolean( 'pgcache.reject.logged' ) ) {
$reject_cookies = \array_merge(
$reject_cookies,
array(
'wordpress_logged_in',
)
);
} elseif ( $w3tc_config->get_boolean( 'pgcache.reject.logged_roles' ) ) {
$new_cookies = array();
foreach ( $w3tc_config->get_array( 'pgcache.reject.roles' ) as $role ) {
$new_cookies = \array_merge( $new_cookies, Util_Cookie::role_cookie_reject_names( $role ) );
}
$reject_cookies = \array_merge( $reject_cookies, $new_cookies );
}
// Custom config.
$reject_cookies = \array_merge( $reject_cookies, $w3tc_config->get_array( 'pgcache.reject.cookie' ) );
Util_Rule::array_trim( $reject_cookies );
// Sanitised here so every consumer excludes the same visitors: preg_quote does not escape
// newlines, and an entry carrying one would land on the next line as a fresh RewriteRule.
return self::sanitize_directive_values( $reject_cookies );
}
/**
* Returns every user-agent token whose presence keeps a visitor out of the page cache.
*
* @since 2.11.0
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @return string[]
*/
public static function reject_user_agents( $w3tc_config ) {
$reject_user_agents = $w3tc_config->get_array( 'pgcache.reject.ua' );
if ( $w3tc_config->get_boolean( 'pgcache.compatibility' ) ) {
$reject_user_agents = \array_merge( array( W3TC_POWERED_BY ), $reject_user_agents );
}
Util_Rule::array_trim( $reject_user_agents );
// Sanitised for the same reason as the cookie list: a newline in an entry would otherwise
// land on the next line as a fresh RewriteRule.
return self::sanitize_directive_values( $reject_user_agents );
}
/**
* Generates the core rules based on the server environment.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
*
* @return string Generated rules as a string.
*/
private function rules_core_generate( $w3tc_config ) {
switch ( true ) {
case Util_Environment::is_apache():
case Util_Environment::is_litespeed():
return $this->rules_core_generate_apache( $w3tc_config );
case Util_Environment::is_nginx():
return $this->rules_core_generate_nginx( $w3tc_config );
}
return '';
}
/**
* Returns the page-enhanced cache URI path for rewrite rules (relative to DOCUMENT_ROOT).
*
* WP-CLI rule generation can compute a different document_root() than Apache uses at
* runtime, so the path is derived from the site URL mapping instead: ABSPATH is served
* at the (network) site URI, e.g. ABSPATH=/var/www/site/wp/ <=> /wp/. This keeps the
* `/wp/` prefix that ABSPATH-relative paths would drop on subdirectory installs.
* When the cache directory lives outside ABSPATH (moved wp-content) or site_uri is
* empty (subdomain at docroot), {@see Util_Environment::site_path()} supplies the
* Apache DOCUMENT_ROOT used for .htaccess rules.
*
* Public because an integration handing delivery to a web-server module has to name the
* same directory W3TC's own rewrite rules do; repeating this arithmetic elsewhere is how
* the two drift apart on a subdirectory install.
*
* @since 2.10.0
*
* @param string $cache_dir Normalized absolute cache directory path.
* @return string
*/
public function apache_cache_uri_path( $cache_dir ) {
$cache_dir = Util_Environment::normalize_path( $cache_dir );
$site_root = realpath( untrailingslashit( ABSPATH ) );
$site_uri = Util_Environment::url_to_uri( network_site_url( '/' ) );
if ( $site_root && 0 === strpos( $cache_dir, $site_root . '/' ) ) {
return $site_uri . substr( $cache_dir, strlen( $site_root ) );
}
$doc_root = null;
if (
$site_root && '' !== $site_uri &&
substr( $site_root, -strlen( $site_uri ) ) === $site_uri
) {
$doc_root = substr( $site_root, 0, -strlen( $site_uri ) );
} else {
$doc_root = realpath( untrailingslashit( Util_Environment::site_path() ) );
}
if ( $doc_root && 0 === strpos( $cache_dir, $doc_root . '/' ) ) {
return substr( $cache_dir, strlen( $doc_root ) );
}
return str_replace( Util_Environment::document_root(), '', $cache_dir );
}
/**
* Generates Apache rewrite rules for page caching.
*
* @param \W3TC\Config $w3tc_config Configuration object containing various settings for the cache.
*
* @return string The generated Apache rewrite rules.
*/
private function rules_core_generate_apache( $w3tc_config ) {
$rewrite_base = Util_Environment::network_home_url_uri();
$cache_dir = Util_Environment::normalize_path( W3TC_CACHE_PAGE_ENHANCED_DIR );
$w3tc_permalink_structure = get_option( 'permalink_structure' );
$current_user = wp_get_current_user();
// True without a condition here, unlike the NGINX generator below: the Apache rules have
// always carried the just-logged-out cookie whatever the engine.
$reject_cookies = self::reject_cookies( $w3tc_config, true );
$reject_user_agents = self::reject_user_agents( $w3tc_config );
// Generate directives.
$env_W3TC_UA = '';
$env_W3TC_REF = '';
$env_W3TC_COOKIE = '';
$env_W3TC_SSL = '';
$env_W3TC_ENC = '';
$rules = '';
$rules .= W3TC_MARKER_BEGIN_PGCACHE_CORE . "\n";
$rules .= "\n";
$rules .= " RewriteEngine On\n";
$rules .= ' RewriteBase ' . $rewrite_base . "\n";
if ( $w3tc_config->get_boolean( 'pgcache.debug' ) ) {
$rules .= " RewriteRule ^(.*\\/)?w3tc_rewrite_test([0-9]+)/?$ $1?w3tc_rewrite_test=1 [L]\n";
}
// Set accept query strings.
$w3tc_query_strings = apply_filters(
'w3tc_pagecache_rules_apache_accept_qs',
$w3tc_config->get_array( 'pgcache.accept.qs' )
);
Util_Rule::array_trim( $w3tc_query_strings );
$env_request_uri = '%{REQUEST_URI}';
if ( ! empty( $w3tc_query_strings ) ) {
$w3tc_query_strings = str_replace( ' ', '+', $w3tc_query_strings );
$w3tc_query_strings = array_map( array( '\W3TC\Util_Environment', 'preg_quote' ), $w3tc_query_strings );
$rules .= " RewriteRule ^ - [E=W3TC_QUERY_STRING:%{QUERY_STRING}]\n";
foreach ( $w3tc_query_strings as $query ) {
$query_rules = array();
if ( strpos( $query, '=' ) === false ) {
$query_rules[] = 'RewriteCond %{ENV:W3TC_QUERY_STRING} ^(.*?&|)' .
$query . '(=[^&]*)?(&.*|)$ [NC]';
$query_rules[] = 'RewriteRule ^ - [E=W3TC_QUERY_STRING:%1%3]';
} else {
$query_rules[] = 'RewriteCond %{ENV:W3TC_QUERY_STRING} ^(.*?&|)' .
$query . '(&.*|)$ [NC]';
$query_rules[] = 'RewriteRule ^ - [E=W3TC_QUERY_STRING:%1%2]';
}
$query_rules = apply_filters(
'w3tc_pagecache_rules_apache_accept_qs_rules',
$query_rules,
$query
);
$rules .= ' ' . implode( "\n ", $query_rules ) . "\n";
}
$rules .= " RewriteCond %{ENV:W3TC_QUERY_STRING} ^&+$\n";
$rules .= " RewriteRule ^ - [E=W3TC_QUERY_STRING]\n";
$rules .= " RewriteCond %{REQUEST_URI} ^([^?]*)(\\?.*)?$\n";
$rules .= " RewriteRule ^ - [E=W3TC_REQUEST_URI:%1]\n";
$env_request_uri = '%{ENV:W3TC_REQUEST_URI}';
}
// Check for mobile redirect.
if ( $w3tc_config->get_boolean( 'mobile.enabled' ) ) {
$mobile_groups = $w3tc_config->get_array( 'mobile.rgroups' );
foreach ( $mobile_groups as $mobile_group => $mobile_config ) {
$mobile_enabled = ( isset( $mobile_config['enabled'] ) ? (bool) $mobile_config['enabled'] : false );
$mobile_agents = ( isset( $mobile_config['agents'] ) ? (array) $mobile_config['agents'] : '' );
$mobile_redirect = Util_Rule::sanitize_directive_value( isset( $mobile_config['redirect'] ) ? (string) $mobile_config['redirect'] : '' );
$mobile_agents = self::sanitize_directive_values( $mobile_agents );
if ( $mobile_enabled && count( $mobile_agents ) && $mobile_redirect ) {
$rules .= ' RewriteCond %{HTTP_USER_AGENT} (' . implode( '|', $mobile_agents ) . ") [NC]\n";
$rules .= ' RewriteRule .* ' . $mobile_redirect . " [R,L]\n";
}
}
}
// Check for referrer redirect.
if ( $w3tc_config->get_boolean( 'referrer.enabled' ) ) {
$referrer_groups = $w3tc_config->get_array( 'referrer.rgroups' );
foreach ( $referrer_groups as $referrer_group => $referrer_config ) {
$referrer_enabled = ( isset( $referrer_config['enabled'] ) ? (bool) $referrer_config['enabled'] : false );
$referrer_referrers = ( isset( $referrer_config['referrers'] ) ? (array) $referrer_config['referrers'] : '' );
$referrer_redirect = Util_Rule::sanitize_directive_value( isset( $referrer_config['redirect'] ) ? (string) $referrer_config['redirect'] : '' );
$referrer_referrers = self::sanitize_directive_values( $referrer_referrers );
if ( $referrer_enabled && count( $referrer_referrers ) && $referrer_redirect ) {
$rules .= ' RewriteCond %{HTTP_COOKIE} w3tc_referrer=.*(' . implode( '|', $referrer_referrers ) . ") [NC]\n";
$rules .= ' RewriteRule .* ' . $referrer_redirect . " [R,L]\n";
}
}
}
// Set mobile groups.
if ( $w3tc_config->get_boolean( 'mobile.enabled' ) ) {
$mobile_groups = array_reverse( $w3tc_config->get_array( 'mobile.rgroups' ) );
foreach ( $mobile_groups as $mobile_group => $mobile_config ) {
$mobile_enabled = ( isset( $mobile_config['enabled'] ) ? (bool) $mobile_config['enabled'] : false );
$mobile_agents = ( isset( $mobile_config['agents'] ) ? (array) $mobile_config['agents'] : '' );
$mobile_redirect = ( isset( $mobile_config['redirect'] ) ? $mobile_config['redirect'] : '' );
$mobile_agents = self::sanitize_directive_values( $mobile_agents );
$mobile_group_s = Util_Rule::sanitize_directive_value( (string) $mobile_group );
if ( $mobile_enabled && count( $mobile_agents ) && ! $mobile_redirect ) {
$rules .= ' RewriteCond %{HTTP_USER_AGENT} (' . implode( '|', $mobile_agents ) . ") [NC]\n";
$rules .= ' RewriteRule .* - [E=W3TC_UA:_' . $mobile_group_s . "]\n";
$env_W3TC_UA = '%{ENV:W3TC_UA}';
}
}
}
// Set referrer groups.
if ( $w3tc_config->get_boolean( 'referrer.enabled' ) ) {
$referrer_groups = array_reverse( $w3tc_config->get_array( 'referrer.rgroups' ) );
foreach ( $referrer_groups as $referrer_group => $referrer_config ) {
$referrer_enabled = ( isset( $referrer_config['enabled'] ) ? (bool) $referrer_config['enabled'] : false );
$referrer_referrers = ( isset( $referrer_config['referrers'] ) ? (array) $referrer_config['referrers'] : '' );
$referrer_redirect = ( isset( $referrer_config['redirect'] ) ? $referrer_config['redirect'] : '' );
$referrer_referrers = self::sanitize_directive_values( $referrer_referrers );
$referrer_group_s = Util_Rule::sanitize_directive_value( (string) $referrer_group );
if ( $referrer_enabled && count( $referrer_referrers ) && ! $referrer_redirect ) {
$rules .= ' RewriteCond %{HTTP_COOKIE} w3tc_referrer=.*(' . implode( '|', $referrer_referrers ) . ") [NC]\n";
$rules .= ' RewriteRule .* - [E=W3TC_REF:_' . $referrer_group_s . "]\n";
$env_W3TC_REF = '%{ENV:W3TC_REF}';
}
}
}
// Set cookie group.
if ( $w3tc_config->get_boolean( 'pgcache.cookiegroups.enabled' ) ) {
$cookie_groups = $w3tc_config->get_array( 'pgcache.cookiegroups.groups' );
foreach ( $cookie_groups as $group_name => $g ) {
if ( isset( $g['enabled'] ) && $g['enabled'] ) {
$cookies = array();
foreach ( $g['cookies'] as $cookie ) {
$cookie = trim( $cookie );
if ( ! empty( $cookie ) ) {
$cookie = str_replace( '+', ' ', $cookie );
$cookie = Util_Rule::sanitize_directive_value( $cookie );
$cookie = Util_Environment::preg_quote( $cookie );
if ( strpos( $cookie, '=' ) === false ) {
$cookie .= '=.*';
}
$cookies[] = $cookie;
}
}
if ( count( $cookies ) > 0 ) {
$cookies_regexp = '^(.*;\s*)?(' . implode( '|', $cookies ) . ')(\s*;.*)?$';
$group_name_s = Util_Rule::sanitize_directive_value( (string) $group_name );
$rules .= " RewriteCond %{HTTP_COOKIE} $cookies_regexp [NC]\n";
$rules .= ' RewriteRule .* - [E=W3TC_COOKIE:_' . $group_name_s . "]\n";
$env_W3TC_COOKIE = '%{ENV:W3TC_COOKIE}';
}
}
}
}
// Set HTTPS.
if ( $w3tc_config->get_boolean( 'pgcache.cache.ssl' ) ) {
$rules .= " RewriteCond %{HTTPS} =on\n";
$rules .= " RewriteRule .* - [E=W3TC_SSL:_ssl]\n";
$rules .= " RewriteCond %{SERVER_PORT} =443\n";
$rules .= " RewriteRule .* - [E=W3TC_SSL:_ssl]\n";
$rules .= " RewriteCond %{HTTP:X-Forwarded-Proto} =https [NC]\n";
$rules .= " RewriteRule .* - [E=W3TC_SSL:_ssl]\n";
$env_W3TC_SSL = '%{ENV:W3TC_SSL}';
}
$cache_path = $this->apache_cache_uri_path( $cache_dir );
// Set Accept-Encoding gzip.
if ( $w3tc_config->get_boolean( 'browsercache.enabled' ) && $w3tc_config->get_boolean( 'browsercache.html.compression' ) ) {
$rules .= " RewriteCond %{HTTP:Accept-Encoding} gzip\n";
$rules .= " RewriteRule .* - [E=W3TC_ENC:_gzip]\n";
$env_W3TC_ENC = '%{ENV:W3TC_ENC}';
}
// Set Accept-Encoding brotli.
if ( $w3tc_config->get_boolean( 'browsercache.enabled' ) && $w3tc_config->get_boolean( 'browsercache.html.brotli' ) ) {
$rules .= " RewriteCond %{HTTP:Accept-Encoding} br\n";
$rules .= " RewriteRule .* - [E=W3TC_ENC:_br]\n";
$env_W3TC_ENC = '%{ENV:W3TC_ENC}';
}
$rules .= " RewriteCond %{HTTP_COOKIE} w3tc_preview [NC]\n";
$rules .= " RewriteRule .* - [E=W3TC_PREVIEW:_preview]\n";
$env_W3TC_PREVIEW = '%{ENV:W3TC_PREVIEW}';
$rules .= " RewriteCond %{REQUEST_URI} \\/$\n";
$rules .= " RewriteRule .* - [E=W3TC_SLASH:_slash]\n";
$env_W3TC_SLASH = '%{ENV:W3TC_SLASH}';
$uri_path_source = ( ! empty( $w3tc_query_strings ) ) ? '%{ENV:W3TC_REQUEST_URI}' : '%{REQUEST_URI}';
$rules .= ' RewriteCond ' . $uri_path_source . " ^/+$\n";
$rules .= " RewriteRule ^ - [E=W3TC_URI_PATH_SLASH:]\n";
$rules .= ' RewriteCond ' . $uri_path_source . " !^/+$\n";
$rules .= ' RewriteCond ' . $uri_path_source . " ^/+(.+?)/?$\n";
$rules .= " RewriteRule ^ - [E=W3TC_URI_PATH_SLASH:%1/]\n";
$env_uri_path_slash = '%{ENV:W3TC_URI_PATH_SLASH}';
$use_cache_rules = '';
// Don't accept POSTs.
$use_cache_rules .= " RewriteCond %{REQUEST_METHOD} !=POST\n";
// Query string should be empty.
$use_cache_rules .= empty( $w3tc_query_strings ) ?
" RewriteCond %{QUERY_STRING} =\"\"\n" :
" RewriteCond %{ENV:W3TC_QUERY_STRING} =\"\"\n";
// Check for rejected cookies. The list arrives sanitised from reject_cookies().
$use_cache_rules .= ' RewriteCond %{HTTP_COOKIE} !(' . implode(
'|',
array_map(
array(
'\W3TC\Util_Environment',
'preg_quote',
),
$reject_cookies
)
) . ") [NC]\n";
// Check for rejected user agents.
if ( count( $reject_user_agents ) ) {
$use_cache_rules .= ' RewriteCond %{HTTP_USER_AGENT} !(' . implode(
'|',
array_map(
array(
'\W3TC\Util_Environment',
'preg_quote',
),
$reject_user_agents
)
) . ") [NC]\n";
}
$use_cache_rules = apply_filters(
'w3tc_pagecache_rules_apache_rewrite_cond',
$use_cache_rules
);
// Make final rewrites for specific files.
$uri_prefix = $cache_path . '/%{HTTP_HOST}/' . $env_uri_path_slash . '_index' . $env_W3TC_SLASH .
$env_W3TC_UA . $env_W3TC_REF . $env_W3TC_COOKIE . $env_W3TC_SSL . $env_W3TC_PREVIEW;
$uri_prefix = apply_filters( 'w3tc_pagecache_rules_apache_uri_prefix', $uri_prefix );
$switch = ' -' . ( $w3tc_config->get_boolean( 'pgcache.file.nfs' ) ? 'F' : 'f' );
$document_root = Util_Rule::apache_docroot_variable();
// write rule to rewrite to .html/.xml file.
$exts = array( '.html' );
if ( $w3tc_config->get_boolean( 'pgcache.cache.nginx_handle_xml' ) ) {
$exts[] = '.xml';
}
/**
* Filter: Allow adding additional rules at the end of the PGCACHE_CORE block, before the last rule.
*
* @since 2.7.1
*
* @param string $rules Additional rules.
* @param string $use_cache_rules Rewrite conditions for non-POST, empty query string, rejected cookies, and rejected user agents.
* @param string $document_root Document root.
* @param string $uri_prefix URI prefix, after the "w3tc_pagecache_rules_apache_uri_prefix" WP filter.
* @param array $exts File extensions used; iterate to use them all.
* @param string $env_W3TC_ENC Encoding string: "", "_br", or "_gzip".
*/
$rules = \apply_filters(
'w3tc_pgcache_rules_apache_last',
$rules,
$use_cache_rules,
$document_root,
$uri_prefix,
$env_W3TC_ENC
);
$has_enc_variant = (
! empty( $env_W3TC_ENC ) &&
'%{ENV:W3TC_ENC}' === $env_W3TC_ENC
);
foreach ( $exts as $w3tc_ext ) {
if ( $has_enc_variant ) {
$rules .= $use_cache_rules;
$rules .= ' RewriteCond "' . $document_root . $uri_prefix . $w3tc_ext . $env_W3TC_ENC .
'"' . $switch . "\n";
$rules .= ' RewriteRule .* "' . $uri_prefix . $w3tc_ext . $env_W3TC_ENC . "\" [L]\n";
}
$rules .= $use_cache_rules;
$rules .= ' RewriteCond "' . $document_root . $uri_prefix . $w3tc_ext .
'"' . $switch . "\n";
$rules .= ' RewriteRule .* "' . $uri_prefix . $w3tc_ext . "\" [L]\n";
}
$rules .= "\n";
$rules .= W3TC_MARKER_END_PGCACHE_CORE . "\n";
return $rules;
}
/**
* Generates NGINX configuration rules for page cache.
*
* @param \W3TC\Config $w3tc_config Configuration object containing various settings for the cache.
*
* @return string NGINX rules for page cache.
*/
private function rules_core_generate_nginx( $w3tc_config ) {
$is_network = Util_Environment::is_wpmu();
$cache_dir = Util_Environment::normalize_path( W3TC_CACHE_PAGE_ENHANCED_DIR );
$w3tc_permalink_structure = get_option( 'permalink_structure' );
$pgcache_engine = $w3tc_config->get_string( 'pgcache.engine' );
$reject_cookies = self::reject_cookies( $w3tc_config, 'file_generic' === $pgcache_engine );
$reject_user_agents = self::reject_user_agents( $w3tc_config );
// Generate rules.
$env_w3tc_ua = '';
$env_w3tc_ref = '';
$env_w3tc_cookie = '';
$env_w3tc_ssl = '';
$env_w3tc_ext = '';
$env_w3tc_enc = '';
$env_request_uri = '$request_uri';
$rules = '';
$rules .= W3TC_MARKER_BEGIN_PGCACHE_CORE . "\n";
if ( $w3tc_config->get_boolean( 'pgcache.debug' ) ) {
$rules .= "rewrite ^(.*\\/)?w3tc_rewrite_test([0-9]+)/?$ $1?w3tc_rewrite_test=1 last;\n";
}
// Set accept query strings.
$w3tc_query_strings = apply_filters(
'w3tc_pagecache_rules_nginx_accept_qs',
$w3tc_config->get_array( 'pgcache.accept.qs' )
);
Util_Rule::array_trim( $w3tc_query_strings );
if ( ! empty( $w3tc_query_strings ) ) {
$w3tc_query_strings = str_replace( ' ', '+', $w3tc_query_strings );
$w3tc_query_strings = array_map( array( '\W3TC\Util_Environment', 'preg_quote' ), $w3tc_query_strings );
$rules .= "set \$w3tc_query_string \$query_string;\n";
foreach ( $w3tc_query_strings as $query ) {
$query_rules = array();
if ( strpos( $query, '=' ) === false ) {
$query_rules[] = 'if ($w3tc_query_string ~* "^(.*?&|)' . $query . '(=[^&]*)?(&.*|)$") {';
$query_rules[] = ' set $w3tc_query_string $1$3;';
$query_rules[] = '}';
} else {
$query_rules[] = 'if ($w3tc_query_string ~* "^(.*?&|)' . $query . '(&.*|)$") {';
$query_rules[] = ' set $w3tc_query_string $1$2;';
$query_rules[] = '}';
}
$query_rules = apply_filters(
'w3tc_pagecache_rules_nginx_accept_qs_rules',
$query_rules,
$query
);
$rules .= implode( "\n", $query_rules ) . "\n";
}
$rules .= "if (\$w3tc_query_string ~ ^[?&]+$) {\n";
$rules .= " set \$w3tc_query_string \"\";\n";
$rules .= "}\n";
$rules .= "set \$w3tc_request_uri \$request_uri;\n";
$rules .= "if (\$w3tc_request_uri ~* \"^([^?]+)\?\") {\n";
$rules .= " set \$w3tc_request_uri \$1;\n";
$rules .= "}\n";
$env_request_uri = '$w3tc_request_uri';
}
// Check for mobile redirect.
if ( $w3tc_config->get_boolean( 'mobile.enabled' ) ) {
$mobile_groups = $w3tc_config->get_array( 'mobile.rgroups' );
foreach ( $mobile_groups as $mobile_group => $mobile_config ) {
$mobile_enabled = ( isset( $mobile_config['enabled'] ) ? (bool) $mobile_config['enabled'] : false );
$mobile_agents = self::sanitize_directive_values( isset( $mobile_config['agents'] ) ? (array) $mobile_config['agents'] : array() );
$mobile_redirect = Util_Rule::sanitize_directive_value( isset( $mobile_config['redirect'] ) ? (string) $mobile_config['redirect'] : '' );
if ( $mobile_enabled && count( $mobile_agents ) && $mobile_redirect ) {
$rules .= 'if ($http_user_agent ~* "(' . implode( '|', $mobile_agents ) . ")\") {\n";
$rules .= ' rewrite .* ' . $mobile_redirect . " last;\n";
$rules .= "}\n";
}
}
}
// Check for referrer redirect.
if ( $w3tc_config->get_boolean( 'referrer.enabled' ) ) {
$referrer_groups = $w3tc_config->get_array( 'referrer.rgroups' );
foreach ( $referrer_groups as $referrer_group => $referrer_config ) {
$referrer_enabled = ( isset( $referrer_config['enabled'] ) ? (bool) $referrer_config['enabled'] : false );
$referrer_referrers = self::sanitize_directive_values( isset( $referrer_config['referrers'] ) ? (array) $referrer_config['referrers'] : array() );
$referrer_redirect = Util_Rule::sanitize_directive_value( isset( $referrer_config['redirect'] ) ? (string) $referrer_config['redirect'] : '' );
if ( $referrer_enabled && count( $referrer_referrers ) &&
$referrer_redirect ) {
$rules .= 'if ($http_cookie ~* "w3tc_referrer=.*(' . implode( '|', $referrer_referrers ) . ")\") {\n";
$rules .= ' rewrite .* ' . $referrer_redirect . " last;\n";
$rules .= "}\n";
}
}
}
// Don't accept POSTs.
$rules .= "set \$w3tc_rewrite 1;\n";
$rules .= "if (\$request_method = POST) {\n";
$rules .= " set \$w3tc_rewrite 0;\n";
$rules .= "}\n";
// Query string should be empty.
$querystring_variable = ( empty( $w3tc_query_strings ) ? '$query_string' : '$w3tc_query_string' );
$rules .= 'if (' . $querystring_variable . " != \"\") {\n";
$rules .= " set \$w3tc_rewrite 0;\n";
$rules .= "}\n";
$rules .= "set \$w3tc_slash \"\";\n";
$rules .= "if ($env_request_uri ~ \\/$) {\n";
$rules .= " set \$w3tc_slash _slash;\n";
$rules .= "}\n";
$env_w3tc_slash = '$w3tc_slash';
// Check for rejected cookies. The list arrives sanitised from reject_cookies().
$rules .= 'if ($http_cookie ~* "(' . implode(
'|',
array_map(
array(
'\W3TC\Util_Environment',
'preg_quote',
),
$reject_cookies
)
) . ")\") {\n";
$rules .= " set \$w3tc_rewrite 0;\n";
$rules .= "}\n";
// Check for rejected user agents.
if ( count( $reject_user_agents ) ) {
$rules .= 'if ($http_user_agent ~* "(' . implode(
'|',
array_map(
array(
'\W3TC\Util_Environment',
'preg_quote',
),
$reject_user_agents
)
) . ")\") {\n";
$rules .= " set \$w3tc_rewrite 0;\n";
$rules .= "}\n";
}
/**
* Filter: Allow extensions to append nginx rewrite-suppression conditions.
*
* Mirrors the Apache "w3tc_pagecache_rules_apache_rewrite_cond" filter. The
* returned string is emitted alongside the built-in reject conditions (POST,
* non-empty query string, rejected cookies / user agents); hooked code should
* append `if (...) { set $w3tc_rewrite 0; }` blocks so matching requests skip
* the static page-cache rewrite and fall through to PHP. The returned string
* is normalized to end with a single newline before it is appended.
*
* @since 2.10.1
*
* @param string $rewrite_conditions Nginx rewrite-suppression conditions buffer (starts empty).
*/
$rewrite_cond = (string) \apply_filters( 'w3tc_pagecache_rules_nginx_rewrite_cond', '' );
if ( '' !== $rewrite_cond ) {
// Normalize to exactly one trailing newline so a callback that omits it cannot glue the next rule on.
$rules .= rtrim( $rewrite_cond, "\n" ) . "\n";
}
// Check mobile groups.
if ( $w3tc_config->get_boolean( 'mobile.enabled' ) ) {
$mobile_groups = array_reverse( $w3tc_config->get_array( 'mobile.rgroups' ) );
$set_ua_var = true;
foreach ( $mobile_groups as $mobile_group => $mobile_config ) {
$mobile_enabled = ( isset( $mobile_config['enabled'] ) ? (bool) $mobile_config['enabled'] : false );
$mobile_agents = self::sanitize_directive_values( isset( $mobile_config['agents'] ) ? (array) $mobile_config['agents'] : array() );
$mobile_redirect = ( isset( $mobile_config['redirect'] ) ? $mobile_config['redirect'] : '' );
$mobile_group_s = Util_Rule::sanitize_directive_value( (string) $mobile_group );
if ( $mobile_enabled && count( $mobile_agents ) && ! $mobile_redirect ) {
if ( $set_ua_var ) {
$rules .= "set \$w3tc_ua \"\";\n";
$set_ua_var = false;
}
$rules .= 'if ($http_user_agent ~* "(' . implode( '|', $mobile_agents ) . ")\") {\n";
$rules .= ' set $w3tc_ua _' . $mobile_group_s . ";\n";
$rules .= "}\n";
$env_w3tc_ua = '$w3tc_ua';
}
}
}
// Check for preview cookie.
$rules .= "set \$w3tc_preview \"\";\n";
$rules .= "if (\$http_cookie ~* \"(w3tc_preview)\") {\n";
$rules .= " set \$w3tc_preview _preview;\n";
$rules .= "}\n";
$env_w3tc_preview = '$w3tc_preview';
// Check referrer groups.
if ( $w3tc_config->get_boolean( 'referrer.enabled' ) ) {
$referrer_groups = array_reverse( $w3tc_config->get_array( 'referrer.rgroups' ) );
$set_ref_var = true;
foreach ( $referrer_groups as $referrer_group => $referrer_config ) {
$referrer_enabled = ( isset( $referrer_config['enabled'] ) ? (bool) $referrer_config['enabled'] : false );
$referrer_referrers = self::sanitize_directive_values( isset( $referrer_config['referrers'] ) ? (array) $referrer_config['referrers'] : array() );
$referrer_redirect = ( isset( $referrer_config['redirect'] ) ? $referrer_config['redirect'] : '' );
$referrer_group_s = Util_Rule::sanitize_directive_value( (string) $referrer_group );
if ( $referrer_enabled && count( $referrer_referrers ) && ! $referrer_redirect ) {
if ( $set_ref_var ) {
$rules .= "set \$w3tc_ref \"\";\n";
$set_ref_var = false;
}
$rules .= 'if ($http_cookie ~* "w3tc_referrer=.*(' . implode( '|', $referrer_referrers ) . ")\") {\n";
$rules .= ' set $w3tc_ref _' . $referrer_group_s . ";\n";
$rules .= "}\n";
$env_w3tc_ref = '$w3tc_ref';
}
}
}
// Set cookie group.
if ( $w3tc_config->get_boolean( 'pgcache.cookiegroups.enabled' ) ) {
$cookie_groups = $w3tc_config->get_array( 'pgcache.cookiegroups.groups' );
$set_cookie_var = true;
foreach ( $cookie_groups as $group_name => $g ) {
if ( isset( $g['enabled'] ) && $g['enabled'] ) {
$cookies = array();
foreach ( $g['cookies'] as $cookie ) {
$cookie = trim( $cookie );
if ( ! empty( $cookie ) ) {
$cookie = str_replace( '+', ' ', $cookie );
$cookie = Util_Rule::sanitize_directive_value( $cookie );
$cookie = Util_Environment::preg_quote( $cookie );
if ( false === strpos( $cookie, '=' ) ) {
$cookie .= '=.*';
}
$cookies[] = $cookie;
}
}
if ( count( $cookies ) > 0 ) {
$cookies_regexp = '"^(.*;)?(' . implode( '|', $cookies ) . ')(;.*)?$"';
$group_name_s = Util_Rule::sanitize_directive_value( (string) $group_name );
if ( $set_cookie_var ) {
$rules .= "set \$w3tc_cookie \"\";\n";
$set_cookie_var = false;
}
$rules .= "if (\$http_cookie ~* $cookies_regexp) {\n";
$rules .= ' set $w3tc_cookie _' . $group_name_s . ";\n";
$rules .= "}\n";
$env_w3tc_cookie = '$w3tc_cookie';
}
}
}
}
if ( $w3tc_config->get_boolean( 'pgcache.cache.ssl' ) ) {
$rules .= "set \$w3tc_ssl \"\";\n";
$rules .= "if (\$scheme = https) {\n";
$rules .= " set \$w3tc_ssl _ssl;\n";
$rules .= "}\n";
$rules .= "if (\$http_x_forwarded_proto = 'https') {\n";
$rules .= " set \$w3tc_ssl _ssl;\n";
$rules .= "}\n";
$env_w3tc_ssl = '$w3tc_ssl';
}
if ( $w3tc_config->get_boolean( 'browsercache.enabled' ) && $w3tc_config->get_boolean( 'browsercache.html.compression' ) ) {
$rules .= "set \$w3tc_enc \"\";\n";
$rules .= "if (\$http_accept_encoding ~ gzip) {\n";
$rules .= " set \$w3tc_enc _gzip;\n";
$rules .= "}\n";
$env_w3tc_enc = '$w3tc_enc';
}
if ( $w3tc_config->get_boolean( 'browsercache.enabled' ) && $w3tc_config->get_boolean( 'browsercache.html.brotli' ) ) {
$rules .= "set \$w3tc_enc \"\";\n";
$rules .= "if (\$http_accept_encoding ~ br) {\n";
$rules .= " set \$w3tc_enc _br;\n";
$rules .= "}\n";
$env_w3tc_enc = '$w3tc_enc';
}
$key_postfix = $env_w3tc_slash . $env_w3tc_ua . $env_w3tc_ref . $env_w3tc_cookie . $env_w3tc_ssl . $env_w3tc_preview;
/**
* Filter: Allow modifying the key_postfix string used in the PGCACHE_CORE block.
*
* @since 2.7.1
*
* @param string $key_postfix Key postfix string.
*/
$key_postfix = \apply_filters( 'w3tc_pgcache_postfix_nginx', $key_postfix );
if ( 'file_generic' === $pgcache_engine ) {
$rules .= $this->for_file_generic(
$w3tc_config,
$cache_dir,
$env_request_uri,
$key_postfix,
$env_w3tc_enc
);
} elseif ( 'nginx_memcached' === $pgcache_engine ) {
$rules .= $this->for_nginx_memcached(
$w3tc_config,
$cache_dir,
$env_request_uri,
$key_postfix,
$env_w3tc_enc
);
}
$rules .= W3TC_MARKER_END_PGCACHE_CORE . "\n";
return $rules;
}
/**
* Generates the cache rules for file-based generic cache handling.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param string $cache_dir The directory where cache files are stored.
* @param string $env_request_uri The request URI for the environment.
* @param string $key_postfix The key postfix to be used in cache keys.
* @param string $env_w3tc_enc The encoded string to append to cache file names.
*
* @return string The generated cache rules for nginx.
*/
private function for_file_generic( $w3tc_config, $cache_dir, $env_request_uri, $key_postfix, $env_w3tc_enc ) {
$rules = '';
$cache_path = $this->apache_cache_uri_path( $cache_dir );
$rules .= "set \$w3tc_uri_path_slash \"\";\n";
$rules .= "if ($env_request_uri ~ ^/+(.+?)/?$) {\n";
$rules .= " set \$w3tc_uri_path_slash \$1/;\n";
$rules .= "}\n";
$uri_prefix = "$cache_path/\$http_host/\$w3tc_uri_path_slash/_index$key_postfix";
$uri_prefix = apply_filters( 'w3tc_pagecache_rules_nginx_uri_prefix', $uri_prefix );
$has_enc_variant = ( ! empty( $env_w3tc_enc ) && '$w3tc_enc' === $env_w3tc_enc );
$rewrite_enc = $env_w3tc_enc;
if ( ! $w3tc_config->get_boolean( 'pgcache.cache.nginx_handle_xml' ) ) {
$env_w3tc_ext = '.html';
if ( $has_enc_variant ) {
$rules .= 'set $w3tc_disk_suffix ".html' . $env_w3tc_enc . '";' . "\n";
$rules .= 'if (!-f "$document_root' . $uri_prefix . '.html' . $env_w3tc_enc . '") {' . "\n";
$rules .= ' set $w3tc_disk_suffix ".html";' . "\n";
$rules .= "}\n";
$rules .= 'if (!-f "$document_root' . $uri_prefix . '$w3tc_disk_suffix") {' . "\n";
$rules .= ' set $w3tc_rewrite 0;' . "\n";
$rules .= "}\n";
$env_w3tc_ext = '$w3tc_disk_suffix';
$rewrite_enc = '';
} else {
$rules .= 'if (!-f "$document_root' . $uri_prefix . '.html") {' . "\n";
$rules .= ' set $w3tc_rewrite 0;' . "\n";
$rules .= "}\n";
}
} else {
$env_w3tc_ext = '$w3tc_ext';
$rules .= 'set $w3tc_ext "";' . "\n";
$rules .= 'if (-f "$document_root' . $uri_prefix . '.html' . $env_w3tc_enc . '") {' . "\n";
$rules .= ' set $w3tc_ext .html;' . "\n";
$rules .= "}\n";
$rules .= 'if (-f "$document_root' . $uri_prefix . '.xml' . $env_w3tc_enc . '") {' . "\n";
$rules .= ' set $w3tc_ext .xml;' . "\n";
$rules .= "}\n";
$rules .= 'if ($w3tc_ext = "") {' . "\n";
$rules .= ' set $w3tc_rewrite 0;' . "\n";
$rules .= "}\n";
}
$rules .= 'if ($w3tc_rewrite = 1) {' . "\n";
$rules .= ' rewrite .* "' . $uri_prefix . $env_w3tc_ext . $rewrite_enc . '" last;' . "\n";
$rules .= "}\n";
return $rules;
}
/**
* Generates the cache rules for nginx with memcached support.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param string $cache_dir The directory where cache files are stored.
* @param string $env_request_uri The request URI for the environment.
* @param string $key_postfix The key postfix to be used in cache keys.
* @param string $env_w3tc_enc The encoded string to append to cache file names.
*
* @return string The generated cache rules for nginx with memcached support.
*/
private function for_nginx_memcached( $w3tc_config, $cache_dir, $env_request_uri, $key_postfix, $env_w3tc_enc ) {
$rules = "set \$request_uri_noslash $env_request_uri;\n";
$rules .= "if ($env_request_uri ~ \"(.*?)(/+)$\") {\n";
$rules .= ' set $request_uri_noslash $1;' . "\n";
$rules .= "}\n";
$cache_path = $this->apache_cache_uri_path( $cache_dir );
$rules .= 'location ~ ".*(?get_boolean( 'browsercache.enabled' ) && $w3tc_config->get_boolean( 'browsercache.html.compression' ) ) {
$rules .= ' memcached_gzip_flag 65536;' . "\n";
}
$rules .= ' default_type text/html;' . "\n";
$memcached_servers = $w3tc_config->get_array( 'pgcache.memcached.servers' );
$memcached_pass = ! empty( $memcached_servers ) ? array_values( $memcached_servers )[0] : 'localhost:11211';
list( $memcached_host, $memcached_port ) = Util_Content::endpoint_to_host_port( $memcached_pass );
$is_unix_socket = (
0 === $memcached_port &&
( 0 === strpos( $memcached_host, 'unix:' ) || false !== strpos( $memcached_host, '/' ) )
);
if ( $is_unix_socket ) {
$memcached_host = preg_replace( '#^unix:(/*)#', '/', $memcached_host );
if ( '/' !== substr( $memcached_host, 0, 1 ) ) {
$memcached_host = '/' . $memcached_host;
}
$memcached_pass = 'unix:' . $memcached_host;
} elseif ( $memcached_port ) {
$memcached_pass = $memcached_host . ':' . $memcached_port;
} else {
$memcached_pass = $memcached_host;
}
$rules .= ' if ($w3tc_rewrite = 1) {' . "\n";
$rules .= ' memcached_pass ' . $memcached_pass . ';' . "\n";
$rules .= " }\n";
$rules .= ' error_page 404 502 504 = @fallback;' . "\n";
$rules .= "}\n";
$rules .= 'location @fallback {' . "\n";
$rules .= ' try_files $uri $uri/ $uri.html /index.php?$args;' . "\n";
$rules .= "}\n";
return $rules;
}
/**
* Adds cache rules to a given set of existing rules.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
* @param Util_Environment_Exceptions $exs The existing set of rules to which new cache rules will be added.
*
* @return void
*/
private function rules_cache_add( $w3tc_config, $exs ) {
Util_Rule::add_rules(
$exs,
Util_Rule::get_pgcache_rules_cache_path(),
$this->rules_cache_generate( $w3tc_config ),
W3TC_MARKER_BEGIN_PGCACHE_CACHE,
W3TC_MARKER_END_PGCACHE_CACHE,
array(
W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE => 0,
W3TC_MARKER_BEGIN_MINIFY_CORE => 0,
W3TC_MARKER_BEGIN_PGCACHE_CORE => 0,
W3TC_MARKER_BEGIN_WORDPRESS => 0,
W3TC_MARKER_END_MINIFY_CACHE => strlen( W3TC_MARKER_END_MINIFY_CACHE ) + 1,
)
);
}
/**
* Removes cache rules from a given set of existing rules.
*
* @param Util_Environment_Exceptions $exs The existing set of rules from which cache rules will be removed.
*
* @return void
*/
private function rules_cache_remove( $exs ) {
// apache's cache files are not used when core rules disabled.
if ( ! Util_Environment::is_nginx() ) {
return;
}
Util_Rule::remove_rules(
$exs,
Util_Rule::get_pgcache_rules_cache_path(),
W3TC_MARKER_BEGIN_PGCACHE_CACHE,
W3TC_MARKER_END_PGCACHE_CACHE
);
}
/**
* Generates cache rules based on the environment (Apache or Nginx).
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
*
* @return string The generated cache rules based on the server environment.
*/
public function rules_cache_generate( $w3tc_config ) {
switch ( true ) {
case Util_Environment::is_apache():
case Util_Environment::is_litespeed():
return $this->rules_cache_generate_apache( $w3tc_config );
case Util_Environment::is_nginx():
return $this->rules_cache_generate_nginx( $w3tc_config );
}
return '';
}
/**
* Generates cache rules for Apache-based environments.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
*
* @return string The generated cache rules for Apache-based environments.
*/
private function rules_cache_generate_apache( $w3tc_config ) {
$charset = get_option( 'blog_charset' );
$pingback_url = get_bloginfo( 'pingback_url' );
$browsercache = $w3tc_config->get_boolean( 'browsercache.enabled' );
$brotli = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.brotli' ) );
$compression = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.compression' ) );
$expires = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.expires' ) );
$lifetime = ( $browsercache ? $w3tc_config->get_integer( 'browsercache.html.lifetime' ) : 0 );
$cache_control = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.cache.control' ) );
$etag = ( $browsercache && $w3tc_config->get_integer( 'browsercache.html.etag' ) );
$w3tc = ( $browsercache && $w3tc_config->get_integer( 'browsercache.html.w3tc' ) );
$compatibility = $w3tc_config->get_boolean( 'pgcache.compatibility' );
$rules = '';
$rules .= W3TC_MARKER_BEGIN_PGCACHE_CACHE . "\n";
if ( $compatibility ) {
$rules .= "Options -MultiViews\n";
// allow to read files by apache if they are blocked at some level above.
$rules .= "\n";
if ( version_compare( Util_Environment::get_server_version(), '2.4', '>=' ) ) {
$rules .= " Require all granted\n";
} else {
$rules .= " Order Allow,Deny\n";
$rules .= " Allow from all\n";
}
$rules .= "\n";
if ( ! $etag ) {
$rules .= "FileETag None\n";
}
}
if ( $w3tc_config->get_boolean( 'pgcache.file.nfs' ) ) {
$rules .= "EnableSendfile Off \n";
}
if ( ! $w3tc_config->get_boolean( 'pgcache.remove_charset' ) ) {
$rules .= 'AddDefaultCharset ' . ( $charset ? $charset : 'utf-8' ) . "\n";
}
if ( $etag ) {
$rules .= "FileETag MTime Size\n";
}
if ( $brotli ) {
$rules .= "\n";
$rules .= " AddType text/html .html_br\n";
$rules .= " AddEncoding br .html_br\n";
$rules .= " AddType text/xml .xml_br\n";
$rules .= " AddEncoding br .xml_br\n";
$rules .= "\n";
$rules .= "\n";
$rules .= " SetEnvIfNoCase Request_URI \\.html_br$ no-brotli\n";
$rules .= " SetEnvIfNoCase Request_URI \\.xml_br$ no-brotli\n";
$rules .= "\n";
}
if ( $compression ) {
$rules .= "\n";
$rules .= " AddType text/html .html_gzip\n";
$rules .= " AddEncoding gzip .html_gzip\n";
$rules .= " AddType text/xml .xml_gzip\n";
$rules .= " AddEncoding gzip .xml_gzip\n";
$rules .= "\n";
$rules .= "\n";
$rules .= " SetEnvIfNoCase Request_URI \\.html_gzip$ no-gzip\n";
$rules .= " SetEnvIfNoCase Request_URI \\.xml_gzip$ no-gzip\n";
$rules .= "\n";
}
if ( $expires ) {
$rules .= "\n";
$rules .= " ExpiresActive On\n";
$rules .= ' ExpiresByType text/html M' . $lifetime . "\n";
$rules .= "\n";
}
$header_rules = '';
if ( $compatibility ) {
$header_rules .= ' Header set X-Pingback "' . $pingback_url . "\"\n";
}
if ( $w3tc ) {
$header_rules .= ' Header set X-Powered-By "' . Util_Environment::w3tc_header() . "\"\n";
}
if ( $expires ) {
$header_rules .= " Header set Vary \"Accept-Encoding, Cookie\"\n";
}
$set_last_modified = $w3tc_config->get_boolean( 'browsercache.html.last_modified' );
if ( ! $set_last_modified && $w3tc_config->get_boolean( 'browsercache.enabled' ) ) {
$header_rules .= " Header unset Last-Modified\n";
}
if ( $cache_control ) {
$cache_policy = $w3tc_config->get_string( 'browsercache.html.cache.policy' );
switch ( $cache_policy ) {
case 'cache':
$header_rules .= " Header set Pragma \"public\"\n";
$header_rules .= " Header set Cache-Control \"public\"\n";
break;
case 'cache_public_maxage':
$header_rules .= " Header set Pragma \"public\"\n";
if ( $expires ) {
$header_rules .= " Header append Cache-Control \"public\"\n";
} else {
$header_rules .= ' Header set Cache-Control "max-age=' . $lifetime . ", public\"\n";
}
break;
case 'cache_validation':
$header_rules .= " Header set Pragma \"public\"\n";
$header_rules .= " Header set Cache-Control \"public, must-revalidate, proxy-revalidate\"\n";
break;
case 'cache_noproxy':
$header_rules .= " Header set Pragma \"public\"\n";
$header_rules .= " Header set Cache-Control \"private, must-revalidate\"\n";
break;
case 'cache_maxage':
$header_rules .= " Header set Pragma \"public\"\n";
if ( $expires ) {
$header_rules .= " Header append Cache-Control \"public, must-revalidate, proxy-revalidate\"\n";
} else {
$header_rules .= ' Header set Cache-Control "max-age=' . $lifetime . ", public, must-revalidate, proxy-revalidate\"\n";
}
break;
case 'no_cache':
$header_rules .= " Header set Pragma \"no-cache\"\n";
$header_rules .= " Header set Cache-Control \"private, no-cache\"\n";
break;
case 'no_store':
$header_rules .= " Header set Pragma \"no-store\"\n";
$header_rules .= " Header set Cache-Control \"no-store\"\n";
break;
}
}
if ( strlen( $header_rules ) > 0 ) {
$rules .= "\n";
$rules .= $header_rules;
$rules .= "\n";
}
$rules .= W3TC_MARKER_END_PGCACHE_CACHE . "\n";
return $rules;
}
/**
* Generates cache rules for Nginx-based environments.
*
* @param Config $w3tc_config W3TC Config containing relevant settings.
*
* @return string The generated cache rules for Nginx-based environments.
*/
private function rules_cache_generate_nginx( $w3tc_config ) {
if ( 'file_generic' !== $w3tc_config->get_string( 'pgcache.engine' ) ) {
return '';
}
$cache_root = Util_Environment::normalize_path( W3TC_CACHE_PAGE_ENHANCED_DIR );
$cache_dir = rtrim( $this->apache_cache_uri_path( $cache_root ), '/' );
if ( Util_Environment::is_wpmu() ) {
$cache_dir = preg_replace( '~/w3tc.*?/~', '/w3tc.*?/', $cache_dir, 1 );
}
$browsercache = $w3tc_config->get_boolean( 'browsercache.enabled' );
$brotli = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.brotli' ) );
$compression = ( $browsercache && $w3tc_config->get_boolean( 'browsercache.html.compression' ) );
$common_rules_a = Dispatcher::nginx_rules_for_browsercache_section( $w3tc_config, 'html', true );
$common_rules = '';
if ( ! empty( $common_rules_a ) ) {
$common_rules = ' ' . implode( "\n ", $common_rules_a ) . "\n";
}
$rules = '';
$rules .= W3TC_MARKER_BEGIN_PGCACHE_CACHE . "\n";
if ( $brotli ) {
$maybe_xml = '';
if ( $w3tc_config->get_boolean( 'pgcache.cache.nginx_handle_xml' ) ) {
$maybe_xml = "\n text/xml xml_br;\n ";
}
$rules .= 'location ~ ' . $cache_dir . ".*br$ {\n";
$rules .= " brotli off;\n";
$rules .= ' types {' . $maybe_xml . "}\n";
$rules .= " default_type text/html;\n";
$rules .= " add_header Content-Encoding br;\n";
$rules .= $common_rules;
$rules .= "}\n";
}
if ( $compression ) {
$maybe_xml = '';
if ( $w3tc_config->get_boolean( 'pgcache.cache.nginx_handle_xml' ) ) {
$maybe_xml = "\n text/xml xml_gzip;\n ";
}
$rules .= 'location ~ ' . $cache_dir . ".*gzip$ {\n";
$rules .= " gzip off;\n";
$rules .= ' types {' . $maybe_xml . "}\n";
$rules .= " default_type text/html;\n";
$rules .= " add_header Content-Encoding gzip;\n";
$rules .= $common_rules;
$rules .= "}\n";
}
$rules .= W3TC_MARKER_END_PGCACHE_CACHE . "\n";
return $rules;
}
}