',
'ExpiresActive On',
'ExpiresDefault "access plus 1 month"',
'ExpiresByType text/css "access plus ' . $css_js_days . ' days"',
'ExpiresByType text/javascript "access plus ' . $css_js_days . ' days"',
'ExpiresByType application/javascript "access plus ' . $css_js_days . ' days"',
'ExpiresByType application/x-javascript "access plus ' . $css_js_days . ' days"',
'ExpiresByType image/jpeg "access plus ' . $image_days . ' days"',
'ExpiresByType image/png "access plus ' . $image_days . ' days"',
'ExpiresByType image/gif "access plus ' . $image_days . ' days"',
'ExpiresByType image/webp "access plus ' . $image_days . ' days"',
'ExpiresByType image/avif "access plus ' . $image_days . ' days"',
'ExpiresByType image/svg+xml "access plus ' . $image_days . ' days"',
'ExpiresByType image/x-icon "access plus ' . $image_days . ' days"',
'ExpiresByType font/woff "access plus ' . $image_days . ' days"',
'ExpiresByType font/woff2 "access plus ' . $image_days . ' days"',
'ExpiresByType font/ttf "access plus ' . $image_days . ' days"',
'ExpiresByType font/otf "access plus ' . $image_days . ' days"',
'ExpiresByType application/font-woff "access plus ' . $image_days . ' days"',
'ExpiresByType application/font-woff2 "access plus ' . $image_days . ' days"',
'ExpiresByType application/vnd.ms-fontobject "access plus ' . $image_days . ' days"',
'',
'',
'',
'Header set Cache-Control "public, max-age=' . $css_js_sec . ', immutable"',
'',
'',
'Header set Cache-Control "public, max-age=' . $image_sec . ', immutable"',
'',
'',
);
return implode( "\n", $lines );
}
/**
* Build GZIP / Brotli negotiation rules.
*
* @param bool $gzip Enable mod_deflate rules.
* @param bool $brotli Enable mod_brotli rules.
* @return string
*/
public static function build_compression_block( $gzip, $brotli ) {
$lines = array();
if ( $gzip ) {
$lines = array_merge(
$lines,
array(
'',
'AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml application/xhtml+xml image/svg+xml',
'BrowserMatch ^Mozilla/4 gzip-only-text/html',
'BrowserMatch ^Mozilla/4\\.0[678] no-gzip',
'BrowserMatch \\bMSIE\\s(7|8) gzip-only-text/html',
'',
)
);
}
if ( $brotli ) {
$lines = array_merge(
$lines,
array(
'',
'AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml application/xhtml+xml image/svg+xml',
'',
)
);
}
return implode( "\n", $lines );
}
/**
* CORS headers so CDN/font files load cross-origin.
*
* @return string
*/
public static function build_font_cors_block() {
return implode( "\n", array(
'',
'',
'Header set Access-Control-Allow-Origin "*"',
'',
'',
) );
}
public static function build_all( array $settings ) {
$parts = array();
$static_cache_rel = self::cache_rel_path();
if ( '' !== $static_cache_rel && ! empty( $settings['page_cache_static_serve'] ) && ( ! empty( $settings['cache_enabled'] ) || ! empty( $settings['page_cache_enabled'] ) ) ) {
$parts[] = self::build_page_cache_serve_block( $static_cache_rel );
}
if ( ! empty( $settings['browser_cache_enabled'] ) ) {
$parts[] = self::build_expires_block(
(int) ( $settings['browser_cache_css_js'] ?? 365 ),
(int) ( $settings['browser_cache_images'] ?? 365 )
);
}
if ( ! empty( $settings['browser_cache_font_cors'] ) ) {
$parts[] = self::build_font_cors_block();
}
$gzip = ! empty( $settings['gzip_enabled'] );
$brotli = ! empty( $settings['brotli_enabled'] );
if ( $gzip || $brotli ) {
$parts[] = self::build_compression_block( $gzip, $brotli );
}
return implode( "\n\n", array_filter( $parts ) );
}
/**
* Resolve the public site's .htaccess location, including subdirectory installs.
* WordPress uses get_home_path() for permalink rule writes; ABSPATH can
* point to a different directory when core is installed separately.
*
* @return string Empty when a safe site root cannot be determined.
*/
private static function htaccess_path() {
if ( ! function_exists( 'get_home_path' ) && defined( 'ABSPATH' ) && is_readable( ABSPATH . 'wp-admin/includes/file.php' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! function_exists( 'get_home_path' ) ) {
return '';
}
$root = get_home_path();
if ( ! is_string( $root ) || '' === $root || ! is_dir( $root ) ) {
return '';
}
return trailingslashit( $root ) . '.htaccess';
}
/**
* Historical API. Sensitive .htaccess backups are now kept in a
* non-autoloaded WordPress option, never in a web-accessible cache file.
*
* @return string Empty: there is deliberately no public backup path.
*/
public static function backup_path() {
return '';
}
/**
* Delete backup files created by previous versions in the public cache.
* The replacement backup lives in the options table.
*/
private static function cleanup_legacy_backup() {
if ( defined( 'QWSC_CACHE_DIR' ) ) {
$legacy = QWSC_CACHE_DIR . 'htaccess.backup';
if ( is_file( $legacy ) ) {
wp_delete_file( $legacy );
}
}
}
/**
* Take a private snapshot of the root .htaccess in the WordPress database.
*
* @return bool Whether the snapshot is available.
*/
public static function backup() {
self::cleanup_legacy_backup();
$htaccess = self::htaccess_path();
if ( ! $htaccess || ! is_readable( $htaccess ) ) {
return false;
}
// Avoid storing unexpectedly large server configuration in the options table.
if ( false !== filesize( $htaccess ) && filesize( $htaccess ) > 1024 * 1024 ) {
return false;
}
$contents = file_get_contents( $htaccess ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- read-only system configuration snapshot.
if ( false === $contents ) {
return false;
}
$option = 'qwsc_private_htaccess_backup';
$stored = update_option( $option, $contents, false );
return $stored || get_option( $option, null ) === $contents;
}
/**
* Restore the private .htaccess snapshot through the WordPress filesystem.
*
* @return bool
*/
public static function restore_backup() {
$contents = get_option( 'qwsc_private_htaccess_backup', null );
if ( ! is_string( $contents ) ) {
return false;
}
$htaccess = self::htaccess_path();
if ( ! $htaccess || ! file_exists( $htaccess ) || ! wp_is_writable( $htaccess ) ) {
return false;
}
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
global $wp_filesystem;
if ( ! WP_Filesystem() || ! $wp_filesystem ) {
return false;
}
return (bool) $wp_filesystem->put_contents( $htaccess, $contents, FS_CHMOD_FILE );
}
/**
* Convert literal backslash-t prefixes from PHP single-quoted strings
* to real indentation before writing Apache or Nginx directives.
*
* @param string[] $lines Rule lines.
* @return string
*/
private static function lines_with_indentation( array $lines ) {
foreach ( $lines as &$line ) {
$level = 0;
while ( 0 === strpos( $line, '\\t' ) ) {
$line = substr( $line, 2 );
++$level;
}
if ( $level ) {
$line = str_repeat( ' ', $level ) . $line;
}
}
unset( $line );
return implode( "\n", $lines );
}
/**
* Ensure Apache static serving precedes WordPress's catch-all rule.
* insert_with_markers() appends a new block to EOF, which may be too late.
* Only the QW Optimize-owned block is moved; WordPress rules are untouched.
*
* @param string $htaccess Apache configuration path.
* @return bool Whether priority is correct.
*/
private static function ensure_static_rules_first( $htaccess ) {
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
global $wp_filesystem;
if ( ! WP_Filesystem() || ! $wp_filesystem ) {
return false;
}
$original = $wp_filesystem->get_contents( $htaccess );
if ( ! is_string( $original ) ) {
return false;
}
if ( ! preg_match( '/^# BEGIN WordPress[ \t]*\r?$/m', $original ) ) {
return true;
}
$pattern = '/^# BEGIN QW Optimize[ \t]*\r?\n.*?^# END QW Optimize[ \t]*(?:\r?\n|$)/ms';
$has_begin = preg_match( '/^# BEGIN QW Optimize[ \t]*\r?$/m', $original );
$has_end = preg_match( '/^# END QW Optimize[ \t]*\r?$/m', $original );
if ( $has_begin !== $has_end ) {
return false;
}
$block = "# BEGIN QW Optimize\n# END QW Optimize\n";
$without = $original;
if ( $has_begin ) {
if ( 1 !== preg_match( $pattern, $original, $match, PREG_OFFSET_CAPTURE ) ) {
return false;
}
$start = $match[0][1];
$block = $match[0][0];
$wp_start = strpos( $original, '# BEGIN WordPress' );
if ( $start < $wp_start ) {
return true;
}
$without = substr_replace( $original, '', $start, strlen( $block ) );
}
if ( ! preg_match( '/^# BEGIN WordPress[ \t]*\r?$/m', $without, $match, PREG_OFFSET_CAPTURE ) ) {
return false;
}
$pos = $match[0][1];
$before = substr( $without, 0, $pos );
$after = substr( $without, $pos );
if ( '' !== $before && "\n" !== substr( $before, -1 ) ) {
$before .= "\n";
}
if ( "\n" !== substr( $block, -1 ) ) {
$block .= "\n";
}
$reordered = $before . $block . $after;
if ( ! $wp_filesystem->put_contents( $htaccess, $reordered, FS_CHMOD_FILE ) ) {
return false;
}
return $reordered === $wp_filesystem->get_contents( $htaccess );
}
/**
* Write or remove plugin-owned rules in the site-root .htaccess (with a private backup).
*
* @param array $settings Plugin settings.
* @return bool True if write attempted successfully or nothing to do.
*/
public static function sync( array $settings ) {
self::cleanup_legacy_backup();
if ( ! function_exists( 'insert_with_markers' ) || ! defined( 'ABSPATH' ) ) {
return false;
}
$htaccess = self::htaccess_path();
// Only touch existing writable .htaccess on Apache-like setups.
if ( ! $htaccess ) {
return false;
}
$rules = self::build_all( $settings );
if ( ! file_exists( $htaccess ) && '' === trim( $rules ) ) {
return true; // No server-level rules were requested.
}
if ( file_exists( $htaccess ) && ! self::backup() ) {
// Never modify a site's server rules if the recovery snapshot fails.
return false;
}
// Place static rules ahead of WordPress's catch-all; fail closed if
// filesystem transport cannot safely make the change.
if ( false !== strpos( $rules, '# QW Optimize — static page cache' )
&& file_exists( $htaccess ) && ! self::ensure_static_rules_first( $htaccess ) ) {
return false;
}
$lines = ( '' === trim( $rules ) ) ? array() : explode( "\n", $rules );
return (bool) insert_with_markers( $htaccess, self::MARKER, $lines );
}
/**
* Remove plugin rules on deactivation/uninstall.
*/
public static function remove() {
if ( ! defined( 'ABSPATH' ) ) {
return;
}
if ( ! function_exists( 'insert_with_markers' ) && file_exists( ABSPATH . 'wp-admin/includes/misc.php' ) ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
}
if ( ! function_exists( 'insert_with_markers' ) ) {
return;
}
$htaccess = self::htaccess_path();
if ( $htaccess && file_exists( $htaccess ) ) {
insert_with_markers( $htaccess, self::MARKER, array() );
}
}
/**
* Path-based page cache before PHP when safe.
* Paths must match PathLayout: pages/{http|https}_{host}/{device}/{rel}/index.html
*
* @param string $cache_rel Relative from web root, trailing slash optional.
* @return string
*/
public static function build_page_cache_serve_block( $cache_rel = 'wp-content/cache/qw-speed-cache/' ) {
$cache_rel = trim( str_replace( '\\', '/', (string) $cache_rel ), '/' );
$mobile_ua = 'Mobile|Android|iPhone|iPad|iPod|webOS|BlackBerry|IEMobile|Opera\\ Mini|Silk/|Kindle';
$cookie_re = 'wordpress_logged_in_|wp-postpass_|woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session_|comment_author_';
// Static Apache rewrites run before WordPress: copy the exact never-cache
// paths into each request guard. Fail closed for ambiguous or overly large
// lists rather than risk serving personalized HTML.
$excluded = \QWSpeedCache\Cache\Exclusions::never_cache_paths();
$guards = array();
$total_length = 0;
foreach ( $excluded as $excluded_path ) {
$excluded_path = (string) $excluded_path;
if ( '' === $excluded_path || '/' !== $excluded_path[0] || ! preg_match( '#\A/[a-zA-Z0-9/._~!$&\x27()*+,;=:@-]*\z#D', $excluded_path ) ) {
return ''; // Use the safe WordPress cache path instead of unsafe Apache rewrites.
}
$parts = array_filter( explode( '/', trim( $excluded_path, '/' ) ), 'strlen' );
if ( $parts ) {
$regex = '^/+' . implode( '/+', array_map( static function ( $part ) { return preg_quote( $part, '#' ); }, $parts ) );
$regex .= '(?:/+index(?:\.html?|\.php)?)?/*$';
} else {
$regex = '^/+(?:index(?:\.html?|\.php)?)?/*$';
}
$guard = '\tRewriteCond %{REQUEST_URI} !' . $regex . ' [NC]';
$total_length += strlen( $guard );
if ( count( $guards ) >= 64 || $total_length > 4096 ) {
return ''; // Avoid excessive .htaccess growth.
}
$guards[] = $guard;
}
$lines = array(
'# QW Optimize — static page cache (serves HTML before PHP)',
'# Layout: /' . $cache_rel . '/pages/{http|https}_HOST/{mobile|desktop}/…/index.html',
'',
'\tHeader set X-QWSC-Cache-Status "HIT-STATIC" env=QWSC_STATIC',
'\tHeader set X-QWSC-Reason "static" env=QWSC_STATIC',
'',
'',
'\tRewriteEngine On',
'# Each static-serving rule has its own fail-closed request checks.',
'# Privacy checks are applied independently to every rule; no skip counters.',
'\t# --- HTTPS + mobile ---',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} ^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} (' . $mobile_ua . ') [NC]',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/https_%{HTTP_HOST}/mobile/$1/index.html -f',
'\tRewriteRule ^(.*)$ /' . $cache_rel . '/pages/https_%{HTTP_HOST}/mobile/$1/index.html [E=QWSC_STATIC:1,L]',
'',
'\t# --- HTTPS + desktop ---',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} ^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} !(' . $mobile_ua . ') [NC]',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/https_%{HTTP_HOST}/desktop/$1/index.html -f',
'\tRewriteRule ^(.*)$ /' . $cache_rel . '/pages/https_%{HTTP_HOST}/desktop/$1/index.html [E=QWSC_STATIC:1,L]',
'',
'\t# --- HTTP + mobile ---',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} !^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} (' . $mobile_ua . ') [NC]',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/http_%{HTTP_HOST}/mobile/$1/index.html -f',
'\tRewriteRule ^(.*)$ /' . $cache_rel . '/pages/http_%{HTTP_HOST}/mobile/$1/index.html [E=QWSC_STATIC:1,L]',
'',
'\t# --- HTTP + desktop ---',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} !^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} !(' . $mobile_ua . ') [NC]',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/http_%{HTTP_HOST}/desktop/$1/index.html -f',
'\tRewriteRule ^(.*)$ /' . $cache_rel . '/pages/http_%{HTTP_HOST}/desktop/$1/index.html [E=QWSC_STATIC:1,L]',
'',
'\t# Homepage edge: empty capture → index/',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} ^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} (' . $mobile_ua . ') [NC]',
'\tRewriteCond %{REQUEST_URI} ^/$',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/https_%{HTTP_HOST}/mobile/index/index.html -f',
'\tRewriteRule ^$ /' . $cache_rel . '/pages/https_%{HTTP_HOST}/mobile/index/index.html [E=QWSC_STATIC:1,L]',
'',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} ^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} !(' . $mobile_ua . ') [NC]',
'\tRewriteCond %{REQUEST_URI} ^/$',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/https_%{HTTP_HOST}/desktop/index/index.html -f',
'\tRewriteRule ^$ /' . $cache_rel . '/pages/https_%{HTTP_HOST}/desktop/index/index.html [E=QWSC_STATIC:1,L]',
'',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} !^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} (' . $mobile_ua . ') [NC]',
'\tRewriteCond %{REQUEST_URI} ^/$',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/http_%{HTTP_HOST}/mobile/index/index.html -f',
'\tRewriteRule ^$ /' . $cache_rel . '/pages/http_%{HTTP_HOST}/mobile/index/index.html [E=QWSC_STATIC:1,L]',
'',
'\tRewriteCond %{REQUEST_METHOD} ^GET$',
'\tRewriteCond %{QUERY_STRING} ^$',
'\tRewriteCond %{HTTP_COOKIE} !(' . $cookie_re . ') [NC]',
'\tRewriteCond %{HTTP:Authorization} ^$',
'\tRewriteCond %{REQUEST_URI} !/(wp-admin|wp-login\.php|xmlrpc\.php|wp-cron\.php|cart|checkout|my-account)(?:/|$|[/?]) [NC]',
'\tRewriteCond %{HTTPS}:%{HTTP:X-Forwarded-Proto} !^(on:.*|.*:https)$ [NC]',
'\tRewriteCond %{HTTP_USER_AGENT} !(' . $mobile_ua . ') [NC]',
'\tRewriteCond %{REQUEST_URI} ^/$',
'\tRewriteCond %{DOCUMENT_ROOT}/' . $cache_rel . '/pages/http_%{HTTP_HOST}/desktop/index/index.html -f',
'\tRewriteRule ^$ /' . $cache_rel . '/pages/http_%{HTTP_HOST}/desktop/index/index.html [E=QWSC_STATIC:1,L]',
'',
);
// All eight Apache static rules carry the same path exclusions.
if ( $guards ) {
$guarded = array();
foreach ( $lines as $line ) {
$guarded[] = $line;
if ( '\tRewriteCond %{QUERY_STRING} ^$' === $line ) {
// Encoded paths can alias a cached file after Apache decodes them.
$guarded[] = '\tRewriteCond %{THE_REQUEST} !%[0-9A-Fa-f]{2} [NC]';
$guarded[] = '\tRewriteCond %{THE_REQUEST} !/\.\.?(?:/|\s|\?) [NC]';
foreach ( $guards as $guard ) {
$guarded[] = $guard;
}
}
}
$lines = $guarded;
}
return self::lines_with_indentation( $lines );
}
/**
* Nginx snippet (manual install — Nginx cannot be auto-written safely).
*
* @param string $cache_rel Relative cache path.
* @return string
*/
public static function build_nginx_page_cache_snippet( $cache_rel = 'wp-content/cache/qw-speed-cache/' ) {
// Nginx static rules are installed manually and cannot receive per-post
// exclusions from WordPress on every edit. Do not recommend a snippet
// that could bypass a post's Never cache setting.
if ( \QWSpeedCache\Cache\Exclusions::never_cache_paths() ) {
return '# QW Optimize: static Nginx serving is unavailable while per-post Never cache exclusions exist.';
}
$cache_rel = trim( str_replace( '\\', '/', (string) $cache_rel ), '/' );
return self::lines_with_indentation(
array(
'# QW Optimize — static page cache',
'# Place inside server { } BEFORE the PHP location block.',
'# Paths match PathLayout: pages/{http|https}_$host/{mobile|desktop}/…/index.html',
'',
'set $qwsc_scheme http;',
'if ($https = on) { set $qwsc_scheme https; }',
'if ($http_x_forwarded_proto = https) { set $qwsc_scheme https; }',
'',
'set $qwsc_device desktop;',
'if ($http_user_agent ~* (Mobile|Android|iPhone|iPad|iPod|webOS|BlackBerry|IEMobile|Opera\\ Mini|Silk/|Kindle)) {',
'\tset $qwsc_device mobile;',
'}',
'',
'set $qwsc_bypass 0;',
'if ($http_cookie ~* (wordpress_logged_in_|wp-postpass_|woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session_|comment_author_)) {',
'\tset $qwsc_bypass 1;',
'}',
'if ($request_method != GET) { set $qwsc_bypass 1; }',
'if ($args != "") { set $qwsc_bypass 1; }',
'if ($request_uri ~* /(wp-admin|wp-login\\.php|xmlrpc\\.php|cart|checkout|my-account)) { set $qwsc_bypass 1; }',
'',
'location / {',
'\tset $qwsc_uri $uri;',
'\tif ($uri = /) { set $qwsc_uri /index; }',
'\tset $qwsc_file $document_root/' . $cache_rel . '/pages/${qwsc_scheme}_$host/$qwsc_device$qwsc_uri/index.html;',
'\tif ($qwsc_bypass = 0) {',
'\t\tadd_header X-QWSC-Cache-Status HIT-STATIC always;',
'\t\ttry_files $qwsc_file $uri $uri/ /index.php?$args;',
'\t}',
'\tif ($qwsc_bypass = 1) {',
'\t\ttry_files $uri $uri/ /index.php?$args;',
'\t}',
'}',
)
);
}
/**
* Return the URL path to the cache only when the filesystem, public URL
* and Apache document root all agree. An .htaccess rewrite must never be
* synthesized from ABSPATH: it may not match the public document root.
*
* @return string Relative path from server document root, or empty to
* disable early Apache static serving safely.
*/
public static function cache_rel_path() {
if ( ! defined( 'QWSC_CACHE_DIR' ) || ! function_exists( 'wp_parse_url' ) || ! function_exists( 'content_url' )
|| ! isset( $_SERVER['DOCUMENT_ROOT'] ) || ! is_string( $_SERVER['DOCUMENT_ROOT'] ) ) {
return '';
}
$document_root = realpath( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) );
$cache_dir = realpath( QWSC_CACHE_DIR );
if ( ! $document_root || ! $cache_dir || ! is_dir( $document_root ) || ! is_dir( $cache_dir ) ) {
return '';
}
$public_url = content_url( 'cache/qw-speed-cache/' );
$public_host = (string) wp_parse_url( $public_url, PHP_URL_HOST );
$site_host = (string) wp_parse_url( home_url( '/' ), PHP_URL_HOST );
$public_path = (string) wp_parse_url( $public_url, PHP_URL_PATH );
if ( '' === $public_path || '' === $public_host || 0 !== strcasecmp( $public_host, $site_host ) ) {
return '';
}
$rel = ltrim( rawurldecode( $public_path ), '/' );
if ( '' === $rel || false !== strpos( $rel, "\0" ) || preg_match( '~(^|/)\.\.(/|$)~', $rel ) ) {
return '';
}
$mapped = realpath( trailingslashit( $document_root ) . $rel );
if ( ! $mapped || wp_normalize_path( $mapped ) !== wp_normalize_path( $cache_dir ) ) {
return '';
}
// Apache rules live in get_home_path()/.htaccess. They cannot reach
// a cache folder outside that .htaccess directory's subtree.
$htaccess = self::htaccess_path();
$home_dir = $htaccess ? realpath( dirname( $htaccess ) ) : false;
if ( ! $home_dir || 0 !== strpos( trailingslashit( wp_normalize_path( $cache_dir ) ), trailingslashit( wp_normalize_path( $home_dir ) ) ) ) {
return '';
}
return trim( str_replace( '\\', '/', $rel ), '/' );
}
/**
* @param array $settings Settings.
* @return array{apache:bool,marker:bool,writable:bool,nginx_hint:string,rel:string}
*/
public static function status( array $settings = array() ) {
$rel = self::cache_rel_path();
$ht = self::htaccess_path();
$writable = $ht && file_exists( $ht ) && wp_is_writable( $ht );
$has_marker = false;
if ( $ht && is_readable( $ht ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading the site's local .htaccess to detect QW Optimize markers.
$c = (string) file_get_contents( $ht );
$has_marker = ( false !== strpos( $c, '# BEGIN ' . self::MARKER ) || false !== strpos( $c, 'HIT-STATIC' ) || false !== strpos( $c, 'QW Optimize — static' ) );
}
$enabled = ! empty( $settings['page_cache_static_serve'] ) && ( ! empty( $settings['cache_enabled'] ) || ! empty( $settings['page_cache_enabled'] ) );
return array(
'enabled' => $enabled,
'apache' => $has_marker,
'writable' => (bool) $writable,
'rel' => $rel,
'nginx' => '' !== $rel ? self::build_nginx_page_cache_snippet( $rel ) : '',
);
}
}