PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.2
1.4.2 1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 36 releases
← All changes | includes/class-host-page-caches.php +292 -107 1.3.2 → 1.4.2 View file →
@@ -47,21 +47,21 @@
47 47 * Detection is by constant and global only, never `is_plugin_active()` on a
48 48 * path string: a renamed plugin folder must not silently turn the integration
49 49 * off. Same rule as Render_Caches.
50 50 *
51 - * Deliberately purge-ALL only. Nginx Helper's per-URL entry point is a method
52 - * call on its purger object rather than an action, and `Cache::purge_url()`
53 - * has no action to hook yet; forwarding single URLs is a separate change once
54 - * that seam lands.
51 + * **This class is the mechanism, not the policy.** It knows how to detect the
52 + * server cache and how to ask Nginx Helper to clear it. WHEN to ask is decided
53 + * one level up, by `Server_Caches::forward()`, from the `intent` and `scope`
54 + * on the purge-event contract — which is also where the reasoning for standing
55 + * down on a content purge is written. Nothing here listens to a hook.
55 56 *
56 - * **Converges with `Server_Caches` later.** PR #348 introduces that class for
57 - * the same idea — forwarding a purge to a cache in front of PHP — with
58 - * LiteSpeed as its first adapter and `xspeed_purge_server_caches` as its
59 - * public seam. Nothing this class does overlaps with it today (different
60 - * server cache, different plugin, and the purge sets are disjoint), so the two
61 - * can land independently. Once #348 is merged, the right shape for this is an
62 - * adapter registered on that filter rather than its own listener; keeping it
63 - * separate now is what avoids editing a branch that is out for re-test.
57 + * Two calls: purge_nginx_helper() clears the whole zone through the action
58 + * above, and purge_nginx_helper_urls() clears named pages through
59 + * `$GLOBALS['nginx_purger']->purge_url()`, a method on its purger object
60 + * rather than an action. That method checks `is_page()`/`is_single()` before
61 + * purging an AMP copy; both only warn when the global `$wp_query` does not
62 + * exist, and `wp-settings.php` creates it for every request, so WP-CLI, cron
63 + * and REST purges are quiet.
64 64 *
65 65 * **Multisite:** nginx keys one cache zone per *install*, not per subsite, so
66 66 * a purge here clears every site on the network at the nginx layer. That is
67 67 * accepted rather than worked around — a cold cache costs one slow request
@@ -74,69 +74,70 @@
74 74 * on multisite, which is why it is read with `get_site_option()`.
75 75 */
76 76 private const NH_OPTION = 'rt_wp_nginx_helper_options';
77 77
78 - /**
79 - * The `cache_method` value that means "nginx FastCGI full-page cache".
80 - * The other one Nginx Helper supports is `enable_redis`, which is a
81 - * page cache in Redis fronted by nginx's `srcache` module — a different
82 - * layer, not present on the hosts this integration targets, and not
83 - * something a purge from here should reach for.
84 - */
78 + /** `cache_method` for nginx's FastCGI page cache, held on disk. */
85 79 private const NH_FASTCGI = 'enable_fastcgi';
86 80
87 81 /**
88 - * Re-entrancy latch. See purge_nginx_helper().
89 - *
90 - * @var bool
82 + * `cache_method` for a page cache held in Redis, fronted by nginx's
83 + * `srcache` module. GridPane offers it, as do EasyEngine and WordOps.
84 + * (#573)
91 85 */
92 - private static bool $purging = false;
86 + private const NH_REDIS = 'enable_redis';
93 87
88 + /** Modes nginx_helper_page_cache() returns. */
89 + public const MODE_FASTCGI = 'fastcgi';
90 + public const MODE_REDIS = 'redis';
91 +
94 92 /**
95 - * Register the listener.
96 - *
97 - * Registration is unconditional and the gate lives in the callback: this
98 - * runs from `Plugin::init()` on `plugins_loaded`, and Nginx Helper builds
99 - * `$GLOBALS['nginx_purger']` from its own `plugins_loaded` callback, so
100 - * load order decides whether a check made here would see it. The callback
101 - * runs during a purge, long after both plugins are up, where the answer
102 - * is stable.
93 + * Nginx Helper's defaults for the keys read here. rtCamp's 2.4.1 and
94 + * GridPane's fork 9.9.10 agree on them (`nginx_helper_default_settings()`).
95 + * They apply when no option row is stored, which is how Nginx Helper runs
96 + * until someone saves its settings page. (#573)
103 97 */
104 - public static function boot(): void {
105 - add_action( 'xspeed_after_purge_all', array( __CLASS__, 'purge_nginx_helper' ), 10, 1 );
106 - }
98 + private const NH_DEFAULTS = array(
99 + 'enable_purge' => 0,
100 + 'cache_method' => self::NH_FASTCGI,
101 + 'purge_method' => 'get_request',
102 + 'redis_hostname' => '127.0.0.1',
103 + 'redis_port' => '6379',
104 + 'redis_prefix' => 'nginx-cache:',
105 + 'redis_unix_socket' => '',
106 + );
107 107
108 + /** Purger classes Nginx Helper builds for its Redis mode. */
109 + private const NH_REDIS_PURGERS = array( 'PhpRedis_Purger', 'Predis_Purger' );
110 +
111 + /** The purger class Nginx Helper builds for anything else. */
112 + private const NH_FASTCGI_PURGER = 'FastCGI_Purger';
113 +
108 114 /**
109 115 * Forward a full purge to the server-level cache, when there is one.
110 116 *
111 - * @param string $cause Who asked. Threaded through for symmetry with the
112 - * other `xspeed_after_purge_all` listeners; Nginx
113 - * Helper's action takes no arguments.
117 + * No gate on the purge's reason here — `Server_Caches::forward()` has
118 + * already decided this purge should reach the server layer. Detection is
119 + * still checked, because it is a fact about the environment rather than
120 + * about the purge, and it is only stable at purge time: Nginx Helper
121 + * builds `$GLOBALS['nginx_purger']` from its own `plugins_loaded`
122 + * callback, so anything asked earlier races its load order.
123 + *
124 + * Re-entrancy is handled upstream. A third-party listener on
125 + * `rt_nginx_helper_purge_all` that calls back into `Cache::purge_all()`
126 + * used to recurse here until PHP ran out of stack, which is what the
127 + * latch this method once carried was for. The inner purge now re-enters
128 + * `Cache::dispatch_purge_event()`, whose `$purge_events_in_flight` guard
129 + * is still held by the outer one and returns before any adapter runs.
130 + * Pinned by a test that recurses through `Cache::purge_all()` itself
131 + * rather than through this method.
132 + *
114 133 * @return bool Whether the purge was forwarded.
115 134 */
116 - public static function purge_nginx_helper( $cause = 'manual' ): bool {
117 - unset( $cause );
118 -
119 - /*
120 - * Nginx Helper's purge is a directory sweep, but it is not OUR code:
121 - * it runs third-party listeners on `rt_nginx_helper_purge_all`, and a
122 - * site can easily have one that calls back into a WordPress purge —
123 - * a "keep every cache in sync" mu-plugin is the common shape. Without
124 - * this latch that lands back in Cache::purge_all(), which fires
125 - * `xspeed_after_purge_all` again, and the two purges recurse until PHP
126 - * runs out of stack. One forward per request is all this integration
127 - * can usefully do anyway, since the second sweep would find an empty
128 - * directory.
129 - */
130 - if ( self::$purging ) {
135 + public static function purge_nginx_helper(): bool {
136 + if ( null === self::nginx_helper_page_cache() ) {
131 137 return false;
132 138 }
133 139
134 - if ( ! self::nginx_helper_is_fastcgi() ) {
135 - return false;
136 - }
137 -
138 - self::$purging = true;
139 140 try {
140 141 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Nginx Helper's own public integration hook; an xspeed_-prefixed name would reach nothing.
141 142 do_action( 'rt_nginx_helper_purge_all' );
142 143 } catch ( \Throwable $e ) {
@@ -146,14 +147,14 @@
146 147 * own. Letting it out would take down `Cache::purge_all()` — the
147 148 * admin presses Purge All, another plugin's mu-plugin fatals, and
148 149 * the failure reads as xSpeed's. The local sweep has already
149 150 * happened by the time we run, so swallowing this costs the
150 - * server layer and nothing else. `Server_Caches::forward()` on
151 - * #348 catches at the same boundary for the same reason.
151 + * server layer and nothing else. `Server_Caches::forward()`
152 + * catches around this call for the same reason and logs it under
153 + * WP_DEBUG; this inner catch is what turns a throw into an honest
154 + * "not forwarded" return rather than a reported purge.
152 155 */
153 156 return false;
154 - } finally {
155 - self::$purging = false;
156 157 }
157 158
158 159 return true;
159 160 }
@@ -158,41 +159,86 @@
158 159 return true;
159 160 }
160 161
161 162 /**
162 - * Whether Nginx Helper is present AND configured against an nginx FastCGI
163 - * cache.
163 + * Purge named pages from the nginx FastCGI cache through Nginx Helper.
164 164 *
165 - * Three separate facts, because each one alone is a false positive:
165 + * Its purger's `purge_url()` is the same call Nginx Helper makes for its
166 + * own post purges. Feeds are not added (`$feed = false`): xSpeed names the
167 + * feeds it means. If the purger cannot take a URL, the whole zone goes
168 + * instead, because a page left stale is worse than a cold cache. The
169 + * same happens when the calls run past `$seconds`: with the `get_request`
170 + * method each one is a blocking HTTP request, and a slow purge endpoint
171 + * would otherwise hold the request for the whole list.
166 172 *
167 - * 1. `RT_WP_NGINX_HELPER_CACHE_PATH` — there is a directory to purge, and
168 - * we know which one, because Health names it.
173 + * @param string[] $urls Absolute URLs on this site.
174 + * @param float $seconds Time allowed for the per-URL calls; 0 for no limit.
175 + */
176 + public static function purge_nginx_helper_urls( array $urls, float $seconds = 0.0 ): bool {
177 + if ( array() === $urls || ! self::nginx_helper_is_fastcgi() ) {
178 + return false;
179 + }
180 + $purger = $GLOBALS['nginx_purger'] ?? null;
181 + if ( ! is_object( $purger ) || ! method_exists( $purger, 'purge_url' ) ) {
182 + return self::purge_nginx_helper();
183 + }
184 + $started = microtime( true );
185 + $left = count( $urls );
186 + try {
187 + foreach ( $urls as $url ) {
188 + $purger->purge_url( (string) $url, false );
189 + --$left;
190 + if ( $left > 0 && $seconds > 0 && microtime( true ) - $started > $seconds ) {
191 + return self::purge_nginx_helper();
192 + }
193 + }
194 + } catch ( \Throwable $e ) {
195 + return self::purge_nginx_helper();
196 + }
197 + return true;
198 + }
199 +
200 + /**
201 + * Which page cache Nginx Helper drives here: MODE_FASTCGI, MODE_REDIS,
202 + * or null when the integration does not apply.
169 203 *
170 - * Do NOT read this as evidence the host configured anything. Nginx
171 - * Helper defines the constant itself whenever it is not already set,
172 - * defaulting to `/var/run/nginx-cache` through its own
204 + * Each fact below alone is a false positive:
205 + *
206 + * 1. `$GLOBALS['nginx_purger']`, the purger Nginx Helper built once it
207 + * finished booting. Its constants and option can outlive a deactivated
208 + * plugin, in which case the action has no listener and firing it is a
209 + * silent no-op we would still report as a purge.
210 + * 2. The mode Nginx Helper itself runs in. The purger's class says it
211 + * outright, since `rt_nginx_helper_purge_all` calls that object's
212 + * purge_all(). For a purger class we do not know, the mode comes from
213 + * the settings Nginx Helper resolved (see nginx_helper_settings()).
214 + * Any method other than FastCGI or Redis is silence.
215 + * 3. For FastCGI, `RT_WP_NGINX_HELPER_CACHE_PATH`: there is a directory
216 + * to purge, and Health names it.
217 + *
218 + * Do NOT read the path as evidence the host configured anything. Nginx
219 + * Helper defines it itself whenever it is not already set, defaulting
220 + * to `/var/run/nginx-cache` through its own
173 221 * `rt_wp_nginx_helper_cache_path` filter (its
174 222 * `includes/class-nginx-helper.php`, in the constructor). So it is
175 - * defined on every install and cannot be missing while the plugin is
176 - * loaded — the only way past this check is a WordPress older than the
177 - * plugin's minimum, where it returns before the `define`.
223 + * defined on every install while the plugin is loaded. On a site where
224 + * the host never set a path, that default is what Nginx Helper would
225 + * unlink, so it is also what Health names; second-guessing another
226 + * plugin's configured path is not ours to do.
178 227 *
179 - * On a site where the host never set a path, that default is what
180 - * Nginx Helper would unlink, so it is also what we forward a purge
181 - * against and what Health names. That directory usually does not
182 - * exist, making its `purge_all()` a no-op we would report as a purge.
183 - * Left alone deliberately: it is exactly what the admin gets from
184 - * Nginx Helper's own Purge All button, and second-guessing another
185 - * plugin's configured path is not ours to do.
186 - * 2. `$GLOBALS['nginx_purger']` — the plugin finished booting and built a
187 - * purger. The constant can be defined in `wp-config.php` by a host
188 - * whose site owner then deactivated the plugin, in which case the
189 - * action has no listener and firing it is a silent no-op we would
190 - * still report as a purge.
191 - * 3. `cache_method === 'enable_fastcgi'` — it is the page cache we mean.
192 - * On `enable_redis` the same action clears a Redis key space that
193 - * xSpeed's own object-cache flush may already own.
228 + * The stored option row alone is not the mode. A host can configure
229 + * Nginx Helper from wp-config.php constants only, and with no row at all
230 + * Nginx Helper runs on its defaults, which are FastCGI. Reading only the
231 + * row missed both on GridPane-style sites, so Purge All never reached a
232 + * server cache Nginx Helper was purging itself. (#573)
194 233 *
234 + * Redis mode used to be skipped on the grounds that its purge clears a key
235 + * space xSpeed's object cache may own. It doesn't: Nginx Helper's Redis
236 + * purge_all() deletes only keys under its own prefix, and xSpeed's
237 + * object-cache flush is scoped to its own salt and never runs FLUSHDB. So
238 + * skipping protected nothing and left the server's page cache stale on
239 + * GridPane, EasyEngine and WordOps sites. (#573)
240 + *
195 241 * Note what is deliberately NOT in the gate: `enable_purge`. That option
196 242 * governs Nginx Helper's own AUTOMATIC purging — its post-save, comment
197 243 * and term hooks each check it — and does not reach `purge_all()`, which
198 244 * runs whatever it is set to. An admin who switched automatic purging off
@@ -199,45 +245,184 @@
199 245 * has said "don't purge behind my back"; they have not said "ignore me
200 246 * when I press Purge All". Reading it as the latter would leave an
201 247 * explicit, operator-initiated purge silently short of the layer actually
202 248 * serving the page, which is the exact failure this integration exists to
203 - * fix.
249 + * fix. `nginx_helper_purges_changes()` does read it, only to decide
250 + * whether a content purge can be left to Nginx Helper.
204 251 */
252 + public static function nginx_helper_page_cache(): ?string {
253 + $purger = $GLOBALS['nginx_purger'] ?? null;
254 + if ( ! is_object( $purger ) ) {
255 + return null;
256 + }
257 + foreach ( self::NH_REDIS_PURGERS as $class ) {
258 + if ( is_a( $purger, $class ) ) {
259 + return self::MODE_REDIS;
260 + }
261 + }
262 + $method = is_a( $purger, self::NH_FASTCGI_PURGER )
263 + ? self::NH_FASTCGI
264 + : (string) self::nginx_helper_settings()['cache_method'];
265 + if ( self::NH_REDIS === $method ) {
266 + return self::MODE_REDIS;
267 + }
268 + if ( self::NH_FASTCGI !== $method || ! defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
269 + return null;
270 + }
271 + $path = (string) constant( 'RT_WP_NGINX_HELPER_CACHE_PATH' );
272 + if ( '' === $path ) {
273 + return null;
274 + }
275 + // With nothing saved and no constant, Nginx Helper runs on its
276 + // defaults: a FastCGI purger aimed at /var/run/nginx-cache whether or
277 + // not nginx caches anything, as on an Apache site where the plugin is
278 + // merely active. Count that only when the directory is there.
279 + if ( ! self::nginx_helper_configured() && ! is_dir( $path ) ) {
280 + return null;
281 + }
282 + return self::MODE_FASTCGI;
283 + }
284 +
285 + /**
286 + * Whether anyone configured Nginx Helper: a saved settings row, or one of
287 + * the constants a host sets. `RT_WP_NGINX_HELPER_CACHE_PATH` is not one
288 + * of them, since Nginx Helper defines it itself.
289 + */
290 + private static function nginx_helper_configured(): bool {
291 + if ( is_array( get_site_option( self::NH_OPTION ) ) ) {
292 + return true;
293 + }
294 + foreach ( array( 'RT_WP_NGINX_HELPER_CACHE_METHOD', 'RT_WP_NGINX_HELPER_REDIS_PREFIX', 'RT_WP_NGINX_HELPER_REDIS_HOSTNAME', 'RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET', 'RT_WP_NGINX_HELPER_PURGE_METHOD' ) as $name ) {
295 + if ( defined( $name ) ) {
296 + return true;
297 + }
298 + }
299 + return false;
300 + }
301 +
302 + /**
303 + * Whether Nginx Helper drives an nginx FastCGI cache, the mode whose
304 + * per-URL purge xSpeed uses. Redis mode takes only whole-zone purges.
305 + */
205 306 public static function nginx_helper_is_fastcgi(): bool {
206 - return null !== self::nginx_helper_cache_path();
307 + return self::MODE_FASTCGI === self::nginx_helper_page_cache();
207 308 }
208 309
209 310 /**
210 - * The cache directory Nginx Helper is pointed at, or null when the
211 - * integration does not apply. Read-only — we never define the constant
212 - * and never write the option.
213 - *
214 - * Health reports the path; the gate only cares whether there is one.
311 + * The cache directory Nginx Helper is pointed at in FastCGI mode, or null.
312 + * Read-only — we never define the constant and never write the option.
215 313 */
216 314 public static function nginx_helper_cache_path(): ?string {
217 - if ( ! defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
315 + if ( self::MODE_FASTCGI !== self::nginx_helper_page_cache() ) {
218 316 return null;
219 317 }
220 - $path = (string) constant( 'RT_WP_NGINX_HELPER_CACHE_PATH' );
221 - if ( '' === $path ) {
318 + return (string) constant( 'RT_WP_NGINX_HELPER_CACHE_PATH' );
319 + }
320 +
321 + /**
322 + * Where a Redis-mode page cache lives, for Health: the server (host:port,
323 + * or the unix socket) and the key prefix Nginx Helper purges. Null outside
324 + * Redis mode. Never the username or password.
325 + *
326 + * @return array{server:string, prefix:string}|null
327 + */
328 + public static function nginx_helper_redis_target(): ?array {
329 + if ( self::MODE_REDIS !== self::nginx_helper_page_cache() ) {
222 330 return null;
223 331 }
224 - if ( ! isset( $GLOBALS['nginx_purger'] ) || ! is_object( $GLOBALS['nginx_purger'] ) ) {
225 - return null;
332 + $settings = self::nginx_helper_settings();
333 + $socket = (string) $settings['redis_unix_socket'];
334 +
335 + return array(
336 + 'server' => '' !== $socket ? $socket : $settings['redis_hostname'] . ':' . $settings['redis_port'],
337 + 'prefix' => (string) $settings['redis_prefix'],
338 + );
339 + }
340 +
341 + /**
342 + * Nginx Helper's settings as Nginx Helper resolved them: the stored row
343 + * over its defaults, with its wp-config.php constants applied. Its purger
344 + * and its automatic purging both run from this, not from the raw row.
345 + *
346 + * `$GLOBALS['nginx_helper_admin']->options` holds exactly that, built in
347 + * the admin class's constructor before the purger is chosen, in rtCamp's
348 + * plugin and in GridPane's fork. When that object is missing we resolve
349 + * the same way ourselves, following the fork, which accepts every
350 + * constant rtCamp's does and a few more.
351 + *
352 + * @return array<string,mixed>
353 + */
354 + private static function nginx_helper_settings(): array {
355 + $admin = $GLOBALS['nginx_helper_admin'] ?? null;
356 + if ( is_object( $admin ) && isset( $admin->options ) && is_array( $admin->options ) ) {
357 + return array_merge( self::NH_DEFAULTS, $admin->options );
226 358 }
227 - $options = get_site_option( self::NH_OPTION );
228 - if ( ! is_array( $options ) || self::NH_FASTCGI !== ( $options['cache_method'] ?? '' ) ) {
229 - return null;
359 +
360 + $stored = get_site_option( self::NH_OPTION );
361 + $settings = array_merge( self::NH_DEFAULTS, is_array( $stored ) ? $stored : array() );
362 +
363 + if ( defined( 'RT_WP_NGINX_HELPER_PURGE_METHOD' ) ) {
364 + $settings['purge_method'] = (string) constant( 'RT_WP_NGINX_HELPER_PURGE_METHOD' );
230 365 }
231 - return $path;
366 +
367 + // GridPane's fork only. FastCGI by constant turns its own purging on
368 + // and ignores the Redis constants.
369 + if ( defined( 'RT_WP_NGINX_HELPER_CACHE_METHOD' ) ) {
370 + $settings['cache_method'] = (string) constant( 'RT_WP_NGINX_HELPER_CACHE_METHOD' );
371 + if ( self::NH_FASTCGI === $settings['cache_method'] ) {
372 + $settings['enable_purge'] = 1;
373 + return $settings;
374 + }
375 + }
376 +
377 + $redis_constants = array(
378 + 'RT_WP_NGINX_HELPER_REDIS_HOSTNAME' => 'redis_hostname',
379 + 'RT_WP_NGINX_HELPER_REDIS_PORT' => 'redis_port',
380 + 'RT_WP_NGINX_HELPER_REDIS_PREFIX' => 'redis_prefix',
381 + 'RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET' => 'redis_unix_socket',
382 + );
383 + foreach ( $redis_constants as $name => $key ) {
384 + if ( defined( $name ) ) {
385 + $settings[ $key ] = (string) constant( $name );
386 + }
387 + }
388 +
389 + // The prefix plus a way to reach Redis switches Nginx Helper to Redis
390 + // mode with its own purging on, whatever the row says. Host and port
391 + // work in both plugins; the fork also accepts a unix socket instead,
392 + // which is how GridPane reaches its page-cache Redis.
393 + $has_prefix = defined( 'RT_WP_NGINX_HELPER_REDIS_PREFIX' );
394 + $by_tcp = $has_prefix && defined( 'RT_WP_NGINX_HELPER_REDIS_HOSTNAME' ) && defined( 'RT_WP_NGINX_HELPER_REDIS_PORT' );
395 + $by_socket = $has_prefix && '' !== (string) ( defined( 'RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET' ) ? constant( 'RT_WP_NGINX_HELPER_REDIS_UNIX_SOCKET' ) : '' );
396 + if ( $by_tcp || $by_socket ) {
397 + $settings['cache_method'] = self::NH_REDIS;
398 + $settings['enable_purge'] = 1;
399 + }
400 +
401 + return $settings;
232 402 }
233 403
234 404 /**
235 - * The configured purge method (`unlink_files`, `get_request`, …), or ''
236 - * when unset. Health uses it to decide whether the permissions caveat
405 + * Whether Nginx Helper purges changed pages by itself.
406 + *
407 + * Its `enable_purge` option is the one switch in front of all of its
408 + * automatic purging: the post, comment and term hooks each return early
409 + * without it. It defaults to off, so an install where the host never
410 + * turned it on purges nothing when a post is published. Read from the
411 + * resolved settings, because the Redis and cache-method constants turn
412 + * it on without touching the row. Not part of the
413 + * detection gate above; `Server_Caches` asks it only to decide whether a
414 + * content purge can be left to Nginx Helper.
415 + */
416 + public static function nginx_helper_purges_changes(): bool {
417 + return ! empty( self::nginx_helper_settings()['enable_purge'] );
418 + }
419 +
420 + /**
421 + * The purge method Nginx Helper resolved (`unlink_files`, `get_request`,
422 + * …), `get_request` by default. Health uses it to decide whether the permissions caveat
237 423 * applies; nothing gates on it.
238 424 */
239 425 public static function nginx_helper_purge_method(): string {
240 - $options = get_site_option( self::NH_OPTION );
241 - return is_array( $options ) ? (string) ( $options['purge_method'] ?? '' ) : '';
426 + return (string) self::nginx_helper_settings()['purge_method'];
242 427 }
243 428 }