', '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 ) : '', ); } }