'xSpeed Cache' !== $label ) ); if ( array() !== $others ) { return (string) $others[0]; } return empty( $active ) ? null : (string) reset( $active ); } /** Is xSpeed's page cache actually serving right now? */ public static function page_cache_is_live(): bool { return Cache::page_cache_operational(); } /** * Turn xSpeed's page cache on, for a host acting on a user's request. * * Null means the cache is SERVING. A string is the reason it is not, * already worded and translated — render it, do not re-interpret it. The * refusal is not an error: installing beside another cache plugin and being * told so is the designed outcome, and the site is fine. * * Two ways to not be serving, and only one of them is a refusal. On a host * that ships wp-config.php read-only the drop-in installs and WP_CACHE * cannot be written, so toggle() reports success with a line for the user * to paste — and a caller reading only `blocked` announced a working cache * that answers every request uncached. status()['page_cache_manual_snippet'] * carries that line. * * Only ever call this because a user asked. Activation deliberately leaves * page caching off, and a host that turns it on unprompted is making a * decision about shared WordPress state on the user's behalf. */ public static function enable_page_cache(): ?string { /* * Not consented, deliberately. A host plugin calling this is acting * on its OWN user's click in its own onboarding — nobody has been * shown whose advanced-cache.php is about to be replaced, which is * the disclosure that makes a takeover legitimate in the dashboard. * * So a competitor's drop-in comes back as a reason the host can * render, exactly as it did before, and the takeover stays something * the site owner does knowingly in xSpeed's own UI. */ $state = Cache::toggle( true, false ); if ( ! empty( $state['blocked'] ) ) { return is_string( $state['blocked_reason'] ) && '' !== $state['blocked_reason'] ? $state['blocked_reason'] : __( 'xSpeed could not enable the page cache on this site.', 'xspeed' ); } if ( ! empty( $state['manual_snippet'] ) ) { return sprintf( /* translators: %s: the PHP line to add to wp-config.php. */ __( 'xSpeed installed its page cache, but wp-config.php is not writable. Add this line to it to start serving: %s', 'xspeed' ), (string) $state['manual_snippet'] ); } return null; } /** The slug of whoever installed xSpeed, or '' when the user did. */ public static function installed_by(): string { return Settings::installed_by(); } /** * Everything a host needs to say whether the install came up right. * * One call, because the alternative is every consumer assembling the same * four reads and drawing its own conclusion from them — which is how three * plugins ended up with three different sentences for one situation. * * `profile` is how a FRESH install came up and is written once, at * activation: `recommended` on a clear site, `conflict-safe` when * something else owned the page cache, `''` when xSpeed was already * installed and nothing was decided. It does not change afterwards — it * records a decision, not the current settings. * * `page_cache_blocked_reason` is non-null only when the cache is NOT live * and there is a nameable reason. A live cache and a clear field both give * null, so it is safe to render whenever it is there. * * `page_cache_manual_snippet` is the wp-config.php line to paste, non-null * only on a host that ships that file read-only. A cache that is not live * and has no blocked reason is almost always this. * * @return array{ * installed:bool, * active:bool, * installed_by:string, * profile:string, * page_cache_live:bool, * page_cache_owner:?string, * page_cache_blocked_reason:?string, * page_cache_manual_snippet:?string * } */ public static function status(): array { $live = self::page_cache_is_live(); return array( 'installed' => self::is_installed(), 'active' => self::is_active(), 'installed_by' => self::installed_by(), 'profile' => Settings::install_profile(), 'page_cache_live' => $live, 'page_cache_owner' => self::page_cache_owner(), 'page_cache_blocked_reason' => $live ? null : Cache::acquisition_blocker(), /* * Not gated on `$live`. Serving and "there is a line to paste" * are independent: on a managed host with an unwritable * wp-config.php the cache serves every hit from * template_redirect while WP_CACHE never landed, so the pre-boot * path is still missing and the user can still act on it. The * helper returns null whenever nothing is needed. */ 'page_cache_manual_snippet' => Cache::manual_wp_cache_snippet(), ); } /** * Pull in get_plugins()/is_plugin_active(), which are admin-only. * * A host can ask these from a front-end or REST request, where the file is * not loaded; and the callers above degrade rather than fatal if it is not * there at all, because a missing answer must not take the site down. */ private static function load_plugin_functions(): void { if ( function_exists( 'get_plugins' ) && function_exists( 'is_plugin_active' ) ) { return; } $file = ABSPATH . 'wp-admin/includes/plugin.php'; if ( file_exists( $file ) ) { require_once $file; } } /** How xSpeed appears in the plugin list. */ private const PLUGIN_FILE = 'xspeed/xspeed.php'; }