Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CloudflareOptimizationHeaderFragment
0.00% covered (danger)
0.00%
0 / 31
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 / 3
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 / 23
0.00% covered (danger)
0.00%
0 / 1
2
 patches
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * CloudflareOptimizationHeaderFragment
4 *
5 * Emits .htaccess rules that set a durable cookie encoding the tuple of
6 * enabled Cloudflare optimizations (e.g., Mirage/Polish/Fonts). The cookie
7 * is set only when absent or mismatched, and is skipped for admin/API routes.
8 *
9 * @package NewfoldLabs\WP\Module\Performance\Cloudflare\Fragments
10 * @since 1.0.0
11 */
12
13namespace NewfoldLabs\WP\Module\Performance\Cloudflare\Fragments;
14
15use NewfoldLabs\WP\Module\Htaccess\Fragment;
16use NewfoldLabs\WP\Module\Htaccess\Context;
17
18/**
19 * Fragment: Cloudflare Optimization Header
20 *
21 * Sets a cookie `nfd-enable-cf-opt=<hash>` (Max-Age=86400, HttpOnly) to signal
22 * which Cloudflare optimizations are currently active on the site, enabling
23 * downstream consumers (e.g., theme/plugin logic, CDN edge logic) to key on it.
24 *
25 * @since 1.0.0
26 */
27final class CloudflareOptimizationHeaderFragment 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     * Deterministic cookie value encoding which CF features are enabled.
45     *
46     * @var string
47     */
48    private $header_value;
49
50    /**
51     * Constructor.
52     *
53     * @since 1.0.0
54     *
55     * @param string $id           Unique fragment ID.
56     * @param string $marker_label Marker label for readability in the file.
57     * @param string $header_value Deterministic cookie value reflecting CF features.
58     */
59    public function __construct( $id, $marker_label, $header_value ) {
60        $this->id           = (string) $id;
61        $this->marker_label = (string) $marker_label;
62        $this->header_value = (string) $header_value;
63    }
64
65    /**
66     * Get the unique fragment ID.
67     *
68     * @since 1.0.0
69     *
70     * @return string Fragment identifier.
71     */
72    public function id() {
73        return $this->id;
74    }
75
76    /**
77     * Execute after core WordPress rules to avoid conflicts.
78     *
79     * @since 1.0.0
80     *
81     * @return int Priority constant.
82     */
83    public function priority() {
84        return self::PRIORITY_POST_WP;
85    }
86
87    /**
88     * Only one instance of this fragment should render.
89     *
90     * @since 1.0.0
91     *
92     * @return bool True if exclusive.
93     */
94    public function exclusive() {
95        return true;
96    }
97
98    /**
99     * Upper-layer logic controls registration; once instantiated, always enabled.
100     *
101     * @since 1.0.0
102     *
103     * @param Context $context Context snapshot (unused).
104     * @return bool True when enabled.
105     */
106    public function is_enabled( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
107        return true;
108    }
109
110    /**
111     * Render the .htaccess rules that set the Cloudflare optimization cookie.
112     *
113     * Skips admin/login/API endpoints; sets an env var when the cookie is absent
114     * or different; then sends `Set-Cookie` via mod_headers conditional on env.
115     *
116     * @since 1.0.0
117     *
118     * @param Context $context Context snapshot (unused).
119     * @return string Rendered fragment including BEGIN/END comments.
120     */
121    public function render( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
122        $value  = $this->header_value;
123        $cookie = 'nfd-enable-cf-opt=' . $value;
124
125        $lines   = array();
126        $lines[] = '# BEGIN ' . $this->marker_label;
127
128        // Gate with mod_rewrite to set an env var only when the cookie is missing/mismatched.
129        $lines[] = '<IfModule mod_rewrite.c>';
130        $lines[] = "\tRewriteEngine On";
131        $lines[] = "\t# Skip setting for admin/API routes";
132        $lines[] = "\tRewriteCond %{REQUEST_URI} !/wp-admin/       [NC]";
133        $lines[] = "\tRewriteCond %{REQUEST_URI} !/wp-login\\.php   [NC]";
134        $lines[] = "\tRewriteCond %{REQUEST_URI} !/wp-json/        [NC]";
135        $lines[] = "\tRewriteCond %{REQUEST_URI} !/xmlrpc\\.php     [NC]";
136        $lines[] = "\tRewriteCond %{REQUEST_URI} !/admin-ajax\\.php [NC]";
137        $lines[] = "\t# Skip if the exact cookie and value are already present";
138        $lines[] = "\tRewriteCond %{HTTP_COOKIE} !(^|;\\s*)" . preg_quote( $cookie, '/' ) . ' [NC]';
139        $lines[] = "\t# Set env var if we passed all conditions";
140        $lines[] = "\tRewriteRule .* - [E=CF_OPT:1]";
141        $lines[] = '</IfModule>';
142
143        // Set the cookie only when env var was set above.
144        $lines[] = '<IfModule mod_headers.c>';
145        $lines[] = "\t# Set cookie only if env var is present (i.e., exact cookie not found)";
146        $lines[] = "\tHeader set Set-Cookie \"" . $cookie . '; path=/; Max-Age=86400; HttpOnly" env=CF_OPT';
147        $lines[] = '</IfModule>';
148
149        $lines[] = '# END ' . $this->marker_label;
150
151        return implode( "\n", $lines );
152    }
153
154    /**
155     * Optional regex patches (none for this fragment).
156     *
157     * @param Context $context Context snapshot (unused).
158     * @return array
159     */
160    public function patches( $context ) {
161        return array();
162    }
163}