Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Skip404Fragment
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 7
56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 priority
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 exclusive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 patches
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Skip404Fragment
4 *
5 * Renders .htaccess rules that stop rewrite processing (L) for requests that
6 * look like static assets (wide extension allowlist) when the request does not
7 * map to an existing file or directory. This avoids funneling such requests
8 * into WordPress' 404 handling.
9 *
10 * @package NewfoldLabs\WP\Module\Performance\Skip404\Fragments
11 * @since 1.0.0
12 */
13
14namespace NewfoldLabs\WP\Module\Performance\Skip404\Fragments;
15
16use NewfoldLabs\WP\Module\Htaccess\Fragment;
17use NewfoldLabs\WP\Module\Htaccess\Context;
18
19/**
20 * Fragment: Skip 404 Handling for Static Files
21 *
22 * Uses mod_rewrite conditions to short-circuit asset-like requests and
23 * prevent WordPress 404 handling, improving performance for missing assets.
24 *
25 * @since 1.0.0
26 */
27final class Skip404Fragment implements Fragment {
28
29    /**
30     * Globally-unique fragment identifier used by the registry.
31     *
32     * @var string
33     */
34    private $id;
35
36    /**
37     * Human-friendly marker text printed in BEGIN/END comments.
38     *
39     * @var string
40     */
41    private $marker_label;
42
43    /**
44     * Constructor.
45     *
46     * @since 1.0.0
47     *
48     * @param string $id           Unique fragment ID used by the registry.
49     * @param string $marker_label Marker label shown in BEGIN/END comments.
50     */
51    public function __construct( $id, $marker_label ) {
52        $this->id           = (string) $id;
53        $this->marker_label = (string) $marker_label;
54    }
55
56    /**
57     * Get the unique fragment ID.
58     *
59     * @since 1.0.0
60     *
61     * @return string Fragment identifier.
62     */
63    public function id() {
64        return $this->id;
65    }
66
67    /**
68     * Execute after core WordPress rules to avoid conflicts.
69     *
70     * @since 1.0.0
71     *
72     * @return int Priority constant.
73     */
74    public function priority() {
75        return self::PRIORITY_POST_WP;
76    }
77
78    /**
79     * Only one instance of this fragment should render.
80     *
81     * @since 1.0.0
82     *
83     * @return bool True if exclusive.
84     */
85    public function exclusive() {
86        return true;
87    }
88
89    /**
90     * Upper-layer logic controls registration; once instantiated, always enabled.
91     *
92     * @since 1.0.0
93     *
94     * @param Context $context Context snapshot (unused).
95     * @return bool True when enabled.
96     */
97    public function is_enabled( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
98        return true;
99    }
100
101    /**
102     * Render the .htaccess rules for skipping WordPress 404 handling on static-like requests.
103     *
104     * @since 1.0.0
105     *
106     * @param Context $context Context snapshot (unused).
107     * @return string Rendered fragment including BEGIN/END comments.
108     */
109    public function render( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
110        $lines   = array();
111        $lines[] = '# BEGIN ' . $this->marker_label;
112        $lines[] = '<IfModule mod_rewrite.c>';
113        $lines[] = "\tRewriteEngine On";
114        $lines[] = "\tRewriteCond %{REQUEST_FILENAME} !-f";
115        $lines[] = "\tRewriteCond %{REQUEST_FILENAME} !-d";
116        $lines[] = "\tRewriteCond %{REQUEST_URI} !(robots\\.txt|ads\\.txt|[a-z0-9_\\-]*sitemap[a-z0-9_\\.\\-]*\\.(xml|xsl|html)(\\.gz)?)";
117        $lines[] = "\tRewriteCond %{REQUEST_URI} \\.(css|htc|less|js|js2|js3|js4|html|htm|rtf|rtx|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|avif|avifs|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|webp|json|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|webm|mpp|otf|_otf|odb|odc|odf|odg|odp|ods|odt|ogg|ogv|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|_ttf|wav|wma|wri|woff|woff2|xla|xls|xlsx|xlt|xlw|zip)$ [NC]";
118        $lines[] = "\tRewriteRule .* - [L]";
119        $lines[] = '</IfModule>';
120        $lines[] = '# END ' . $this->marker_label;
121
122        return implode( "\n", $lines );
123    }
124
125    /**
126     * Inject a no-rewrite for missing static assets into the core WP block.
127     *
128     * We insert just before: "RewriteRule . /index.php [L]".
129     * The existing WP conditions (!-f, !-d) continue to apply to our inserted rule.
130     *
131     * @param Context $context Context snapshot (unused).
132     * @return array
133     */
134    public function patches( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
135        $id = $this->id;
136
137        // Self-contained snippet: includes !-f and !-d so it never catches real files/dirs.
138        $snippet  = "# NFD PATCH {$id} BEGIN\n";
139        $snippet .= "RewriteCond %{REQUEST_FILENAME} !-f\n";
140        $snippet .= "RewriteCond %{REQUEST_FILENAME} !-d\n";
141        $snippet .= "RewriteCond %{REQUEST_URI} !(robots\\.txt|ads\\.txt|[a-z0-9_\\-]*sitemap[a-z0-9_\\.\\-]*\\.(xml|xsl|html)(\\.gz)?)\n";
142        $snippet .= "RewriteCond %{REQUEST_URI} \\.(css|htc|less|js|js2|js3|js4|html|htm|rtf|rtx|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|avif|avifs|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|webp|json|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|webm|mpp|otf|_otf|odb|odc|odf|odg|odp|ods|odt|ogg|ogv|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|_ttf|wav|wma|wri|woff|woff2|xla|xls|xlsx|xlt|xlw|zip)$ [NC]\n";
143        $snippet .= "RewriteRule .* - [L]\n";
144        $snippet .= "# NFD PATCH {$id} END\n";
145
146        return array(
147            array(
148                'scope'       => 'wp_block',
149                // Find the two WP guards as a unit, and insert our snippet BEFORE them.
150                'pattern'     => '~^(?=[ \t]*RewriteCond[^\n]*%{REQUEST_FILENAME}\s+!-f\s*\R[ \t]*RewriteCond[^\n]*%{REQUEST_FILENAME}\s+!-d\s*\R)~m',
151                'replacement' => $snippet,
152                'limit'       => 1,
153            ),
154        );
155    }
156}