' ) ) { return $html; } $t0 = microtime( true ); $processed = Velox_Optimizer::process( $html ); if ( ! is_string( $processed ) || '' === $processed ) { $processed = $html; } $t1 = microtime( true ); if ( Velox_Optimizer::$css_pending && ! headers_sent() ) { header( 'X-Velox-CSS: pending' ); // Tells the preloader to fetch the page once more. } // A page built before its critical CSS existed is not stored; the next build includes it. if ( self::is_cacheable() && ! Velox_Optimizer::$css_pending ) { self::store( $processed ); Velox_Server::count_build(); } if ( ! headers_sent() ) { // Standard Server-Timing header (browser DevTools > Network > Timing): where the time of an // uncached request went. Cached pages are static files and never carry it. $start = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? (float) $_SERVER['REQUEST_TIME_FLOAT'] : $t0; header( sprintf( 'Server-Timing: wp;desc="WordPress";dur=%.1f, vxopt;desc="Optimize";dur=%.1f, vxcss;desc="CSS";dur=%.1f, vxstore;desc="Cache store";dur=%.1f', ( $t0 - $start ) * 1000, ( $t1 - $t0 ) * 1000, Velox_Optimizer::$css_ms, ( microtime( true ) - $t1 ) * 1000 ) ); } return $processed; } public static function is_cacheable() { if ( ! Velox_Settings::get( 'cache_enabled' ) ) { return false; } if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) { return false; } if ( is_user_logged_in() || is_search() || is_404() || is_feed() || is_preview() || is_customize_preview() ) { return false; } if ( is_singular() && post_password_required() ) { return false; } if ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() || is_account_page() ) ) { return false; } $code = (int) http_response_code(); if ( $code && 200 !== $code ) { return false; } foreach ( headers_list() as $h ) { if ( 0 === stripos( $h, 'Set-Cookie:' ) ) { return false; } } $uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/'; // A page rendered with any query string is never stored, not even with tracking parameters: // themes and plugins may print those values (utm_* into forms or links), and the stored copy // is shown to everyone. Visitors with tracking parameters still get the cached clean page from // the drop-in once it exists. if ( '' !== (string) wp_parse_url( $uri, PHP_URL_QUERY ) ) { return false; } foreach ( $_COOKIE as $name => $v ) { if ( preg_match( '/^(wordpress_logged_in_|wp-postpass_|comment_author_|woocommerce_items_in_cart|wp_woocommerce_session_)/', (string) $name ) ) { return false; } } // Only cache for the site's own host (prevents Host header abuse). $host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( preg_replace( '/:\d+$/', '', sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) ) : ''; $home_host = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); if ( '' === $host || $host !== $home_host ) { return false; } $path = (string) wp_parse_url( $uri, PHP_URL_PATH ); foreach ( self::exclude_regexes() as $re ) { if ( preg_match( $re, $path ) ) { return false; } } // Other plugins can keep a page out of the cache (they can never force one in). return (bool) apply_filters( 'velox_is_cacheable', true ); } /** * Explains, for the current front-end request, whether an anonymous visitor would get this page * from the cache and if not, why. Used by the admin bar. Returns [ cacheable(bool), reasons[] , has_file(bool) ]. */ public static function explain() { $why = array(); if ( ! Velox_Settings::get( 'cache_enabled' ) ) { $why[] = __( 'page cache is disabled', 'angelforce-cache' ); } if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) { $why[] = __( 'a plugin set DONOTCACHEPAGE', 'angelforce-cache' ); } if ( is_search() ) { $why[] = __( 'search results', 'angelforce-cache' ); } if ( is_404() ) { $why[] = __( '404 page', 'angelforce-cache' ); } if ( is_feed() ) { $why[] = __( 'feed', 'angelforce-cache' ); } if ( is_preview() || is_customize_preview() ) { $why[] = __( 'preview', 'angelforce-cache' ); } if ( is_singular() && post_password_required() ) { $why[] = __( 'password protected', 'angelforce-cache' ); } if ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() || is_account_page() ) ) { $why[] = __( 'WooCommerce cart / checkout / account page', 'angelforce-cache' ); } $uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/'; $q = (string) wp_parse_url( $uri, PHP_URL_QUERY ); if ( '' !== $q ) { parse_str( $q, $params ); foreach ( array_keys( $params ) as $k ) { if ( ! preg_match( '/^(utm_|fbclid|gclid|msclkid|mc_|_ga|yclid|ttclid|twclid|dclid|srsltid)/', (string) $k ) ) { $why[] = sprintf( /* translators: %s: query parameter name */ __( 'query parameter "%s"', 'angelforce-cache' ), $k ); break; } } } $path = (string) wp_parse_url( $uri, PHP_URL_PATH ); foreach ( self::exclude_regexes() as $re ) { if ( preg_match( $re, $path ) ) { $why[] = __( 'URL is in the exclusion list', 'angelforce-cache' ); break; } } $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $variant = false; if ( Velox_Settings::get( 'webp' ) ) { $variant = ( Velox_Settings::get( 'avif' ) && Velox_Optimizer::accepts_avif() ) ? 'avif' : ( Velox_Optimizer::accepts_webp() ? true : false ); } $file = self::file_for( $host, $uri, false, self::request_is_https(), $variant ); return array( empty( $why ), $why, is_file( $file ) ); } public static function exclude_regexes() { $lines = preg_split( '/\r\n|\r|\n/', (string) Velox_Settings::get( 'cache_exclude' ) ); $lines = array_merge( array( '/wp-admin/*', '/wp-login.php', '/wp-json/*', '/xmlrpc.php' ), $lines ); $out = array(); foreach ( $lines as $line ) { $line = trim( $line ); if ( '' === $line ) { continue; } $out[] = '#^' . str_replace( '\*', '.*', preg_quote( $line, '#' ) ) . '$#i'; } return $out; } public static function store( $html ) { $https = self::request_is_https(); $mobile = Velox_Settings::get( 'cache_mobile' ) && self::is_mobile_ua(); $webp = false; if ( Velox_Settings::get( 'webp' ) ) { if ( Velox_Settings::get( 'avif' ) && Velox_Optimizer::accepts_avif() ) { $webp = 'avif'; } elseif ( Velox_Optimizer::accepts_webp() ) { $webp = true; } } $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/'; $file = self::file_for( $host, $uri, $mobile, $https, $webp ); $dir = dirname( $file ); if ( ! wp_mkdir_p( $dir ) ) { return false; } self::protect_dir(); $html .= "\n'; $tmp = $file . '.' . wp_generate_password( 6, false ) . '.tmp'; if ( false === file_put_contents( $tmp, $html, LOCK_EX ) ) { return false; } if ( ! @rename( $tmp, $file ) ) { wp_delete_file( $tmp ); return false; } // Pre-compressed copies: the web server sends them as-is, no CPU spent on compression per request. // Brotli (smaller) when the PHP extension exists, gzip always. if ( function_exists( 'brotli_compress' ) ) { $br = brotli_compress( $html, 9 ); if ( false !== $br ) { @file_put_contents( $file . '.br', $br, LOCK_EX ); } } else { wp_delete_file( $file . '.br' ); } if ( function_exists( 'gzencode' ) ) { $gz = gzencode( $html, 6 ); if ( false !== $gz ) { @file_put_contents( $file . '.gz', $gz, LOCK_EX ); } } else { wp_delete_file( $file . '.gz' ); } return true; } private static function protect_dir() { $base = self::dir(); if ( ! file_exists( $base . 'index.html' ) ) { @file_put_contents( $base . 'index.html', '' ); } if ( is_file( $base . 'index.php' ) ) { wp_delete_file( $base . 'index.php' ); // Written by versions before 2.2.0. } // Data files (settings for the drop-in, logs, CSS records) are never served to visitors. $rules = "Options -Indexes\n\n\nRequire all denied\n\n\nOrder allow,deny\nDeny from all\n\n\n"; if ( ! file_exists( $base . '.htaccess' ) || (string) file_get_contents( $base . '.htaccess' ) !== $rules ) { @file_put_contents( $base . '.htaccess', $rules ); } } /* ---------- purge ---------- */ public static function on_save_post( $post_id, $post ) { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } if ( ! $post || ! in_array( $post->post_status, array( 'publish', 'trash', 'private' ), true ) ) { return; } self::purge_post_id( $post_id ); } public static function purge_post_id( $post_id ) { $post = get_post( $post_id ); if ( ! $post || ! is_post_type_viewable( $post->post_type ) ) { return; } self::purge_url( get_permalink( $post ) ); self::purge_url( home_url( '/' ) ); $archive = get_post_type_archive_link( $post->post_type ); if ( $archive ) { self::purge_url( $archive ); } $taxes = get_object_taxonomies( $post->post_type ); foreach ( $taxes as $tax ) { $terms = get_the_terms( $post, $tax ); if ( is_array( $terms ) ) { foreach ( $terms as $term ) { $link = get_term_link( $term ); if ( ! is_wp_error( $link ) ) { self::purge_url( $link ); } } } } self::purge_url( get_author_posts_url( $post->post_author ) ); $blog = (int) get_option( 'page_for_posts' ); if ( $blog ) { self::purge_url( get_permalink( $blog ) ); } } public static function on_comment( $comment_id, $approved ) { $c = get_comment( $comment_id ); if ( $c && 1 === (int) $approved ) { self::purge_url( get_permalink( $c->comment_post_ID ) ); } } public static function on_comment_status( $new, $old, $comment ) { if ( $comment && isset( $comment->comment_post_ID ) ) { self::purge_url( get_permalink( $comment->comment_post_ID ) ); } } public static function purge_url( $url ) { if ( ! $url ) { return; } Velox_Cloudflare::queue_url( $url ); $host = (string) wp_parse_url( $url, PHP_URL_HOST ); $path = (string) wp_parse_url( $url, PHP_URL_PATH ); $dir = dirname( self::file_for( $host, $path, false, false ) ); foreach ( glob( $dir . '/index*.html*' ) ?: array() as $file ) { wp_delete_file( $file ); } do_action( 'velox_purged_url', $url ); } public static function purge_all() { self::rrmdir( self::pages_dir(), false ); Velox_Cloudflare::queue_all(); Velox_CSS::purge(); update_option( 'velox_last_purge', time(), false ); delete_transient( 'velox_stats' ); Velox_Preload::schedule(); do_action( 'velox_purged_all' ); } public static function rrmdir( $dir, $remove_self = true ) { if ( ! is_dir( $dir ) ) { return; } $items = @scandir( $dir ); if ( ! $items ) { return; } foreach ( $items as $item ) { if ( '.' === $item || '..' === $item ) { continue; } $path = $dir . '/' . $item; if ( is_dir( $path ) && ! is_link( $path ) ) { self::rrmdir( $path, true ); } else { wp_delete_file( $path ); } } if ( $remove_self ) { @rmdir( $dir ); } } public static function clean_expired() { $life = max( 1, (int) Velox_Settings::get( 'cache_lifespan' ) ) * HOUR_IN_SECONDS; self::walk( self::pages_dir(), function ( $file ) use ( $life ) { if ( time() - filemtime( $file ) > $life ) { wp_delete_file( $file ); } } ); // Parsed stylesheet caches are rebuilt on demand; drop month-old ones (stale theme versions). foreach ( array_merge( glob( self::dir() . 'css/p-*.php' ) ?: array(), glob( self::dir() . 'css/p-*.json' ) ?: array() ) as $file ) { if ( time() - filemtime( $file ) > 30 * DAY_IN_SECONDS ) { wp_delete_file( $file ); } } self::walk( self::dir() . 'min/', function ( $file ) { if ( time() - filemtime( $file ) > 30 * DAY_IN_SECONDS ) { wp_delete_file( $file ); } } ); } private static function walk( $dir, $cb ) { if ( ! is_dir( $dir ) ) { return; } $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ) ); foreach ( $it as $file ) { if ( $file->isFile() ) { call_user_func( $cb, $file->getPathname() ); } } } public static function stats() { $cached = get_transient( 'velox_stats' ); if ( is_array( $cached ) ) { return $cached; } $count = 0; $size = 0; self::walk( self::pages_dir(), function ( $file ) use ( &$count, &$size ) { if ( preg_match( '/index(-mobile)?(-https)?\.html$/', $file ) ) { $count++; $size += filesize( $file ); } } ); $out = array( 'pages' => $count, 'size' => $size, ); set_transient( 'velox_stats', $out, 5 * MINUTE_IN_SECONDS ); return $out; } /* ---------- drop-in config ---------- */ public static function write_config() { wp_mkdir_p( self::dir() ); wp_mkdir_p( self::pages_dir() ); self::protect_dir(); $cfg = array( 'enabled' => (bool) Velox_Settings::get( 'cache_enabled' ), 'mobile' => (bool) Velox_Settings::get( 'cache_mobile' ), 'lifespan' => max( 1, (int) Velox_Settings::get( 'cache_lifespan' ) ) * HOUR_IN_SECONDS, 'exclude' => self::exclude_regexes(), 'webp' => (bool) Velox_Settings::get( 'webp' ), 'avif' => (bool) ( Velox_Settings::get( 'webp' ) && Velox_Settings::get( 'avif' ) ), 'host' => strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ), 'brotli' => function_exists( 'brotli_compress' ), 'brotli_dynamic' => (bool) Velox_Settings::get( 'brotli_dynamic' ), ); // Plain JSON data read by the advanced-cache.php drop-in (blocked from web access by the cache folder's .htaccess). @file_put_contents( self::dir() . 'config.json', wp_json_encode( $cfg ), LOCK_EX ); if ( is_file( self::dir() . 'config.php' ) ) { wp_delete_file( self::dir() . 'config.php' ); // Written by versions before 2.2.0. } } public static function dropin_path() { return trailingslashit( WP_CONTENT_DIR ) . 'advanced-cache.php'; } public static function dropin_is_ours() { $f = self::dropin_path(); return is_file( $f ) && 1 === preg_match( '/(AngelForce|Velox) Cache drop-in/', (string) file_get_contents( $f, false, null, 0, 600 ) ); } public static function install_dropin() { $f = self::dropin_path(); if ( file_exists( $f ) && ! self::dropin_is_ours() ) { return false; // Another plugin owns it. } return @copy( VELOX_DIR . 'dropin/advanced-cache.php', $f ); } public static function remove_dropin() { if ( self::dropin_is_ours() ) { wp_delete_file( self::dropin_path() ); } } public static function wp_config_path() { $f = ABSPATH . 'wp-config.php'; if ( ! file_exists( $f ) ) { $f = dirname( ABSPATH ) . '/wp-config.php'; } return is_file( $f ) ? $f : false; } public static function set_wp_cache( $on ) { $f = self::wp_config_path(); if ( ! $f || ! is_writable( $f ) ) { return false; } $c = file_get_contents( $f ); $old = $c; if ( $on ) { if ( preg_match( "/define\(\s*['\"]WP_CACHE['\"]\s*,\s*true\s*\)/i", $c ) ) { return true; } if ( preg_match( "/define\(\s*['\"]WP_CACHE['\"]\s*,\s*false\s*\)/i", $c ) ) { $c = preg_replace( "/define\(\s*['\"]WP_CACHE['\"]\s*,\s*false\s*\)/i", "define( 'WP_CACHE', true ) /* " . self::MARK . ' */', $c, 1 ); } else { $c = preg_replace( '/<\?php/', "