PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.2.4
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.2.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 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
xspeed / includes / class-host.php

class-host.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.2.4, at includes/class-host.php

217 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Host — everything another WPDeveloper plugin needs from xSpeed.
4 *
5 * EmbedPress, Templately and Essential Addons can install and activate xSpeed
6 * for a user. They used to do it by copy-vendoring a detector and a settings
7 * writer out of this repo, which meant every one of them carried a second
8 * implementation of rules that live here, and a fix had to be re-copied into
9 * each before it reached anybody.
10 *
11 * They do not need any of that now: xSpeed always installs, and works out for
12 * itself how to come up (Settings::set_defaults()). What is left is this
13 * class — one place a host may call into, so nothing has to reach into Cache
14 * or Page_Cache_Detector and nothing has to be duplicated.
15 *
16 * The one thing NOT here is provenance, because a host records it before any
17 * of this code exists:
18 *
19 * update_option( 'xspeed_installed_by', 'essential-addons' );
20 * // then install + activate as normal
21 *
22 * See IMPLEMENTATION.md §6.2d.
23 *
24 * @package XSpeed
25 */
26
27 namespace XSpeed;
28
29 defined( 'ABSPATH' ) || exit;
30
31 final class Host {
32
33 /**
34 * Is xSpeed installed on this site at all?
35 *
36 * A host asks before offering to install it. Reads the plugin list rather
37 * than a constant, because the question is asked from a request where
38 * xSpeed may not be loaded.
39 */
40 public static function is_installed(): bool {
41 self::load_plugin_functions();
42 if ( ! function_exists( 'get_plugins' ) ) {
43 return false;
44 }
45 return array_key_exists( self::PLUGIN_FILE, (array) get_plugins() );
46 }
47
48 /** Is it active — on this site, or network-wide? */
49 public static function is_active(): bool {
50 self::load_plugin_functions();
51 return function_exists( 'is_plugin_active' ) && (bool) is_plugin_active( self::PLUGIN_FILE );
52 }
53
54 /**
55 * Does anything already own the page cache?
56 *
57 * A host does NOT have to consult this before installing — xSpeed installs
58 * beside anything and comes up with everything switched off. It is here
59 * for a host that wants to say what it found, or to decide whether to
60 * bother asking the user about page caching at all.
61 */
62 public static function page_cache_is_free(): bool {
63 return Page_Cache_Detector::is_field_clear();
64 }
65
66 /**
67 * Who owns the page cache, as a name to put in a sentence, or null.
68 *
69 * Prefers whoever owns the drop-in, since that is proof; falls back to the
70 * first active plugin that could be caching, which is the honest answer
71 * for something like LiteSpeed that caches at server level.
72 */
73 public static function page_cache_owner(): ?string {
74 $owner = Page_Cache_Detector::dropin_owner_label();
75 if ( is_string( $owner ) && '' !== $owner ) {
76 return $owner;
77 }
78 /*
79 * The catalog counts xSpeed among the page caches, correctly, so it
80 * turns up here on any site running us. It is not the answer a host
81 * is after: if we owned the drop-in the branch above would already
82 * have said so, and without it we are one more plugin that COULD be
83 * caching. Name a competitor first and fall back to ourselves.
84 */
85 $active = Page_Cache_Detector::active_page_caches();
86 $others = array_values( array_filter( $active, static fn ( $label ) => 'xSpeed Cache' !== $label ) );
87 if ( array() !== $others ) {
88 return (string) $others[0];
89 }
90 return empty( $active ) ? null : (string) reset( $active );
91 }
92
93 /** Is xSpeed's page cache actually serving right now? */
94 public static function page_cache_is_live(): bool {
95 return Cache::page_cache_operational();
96 }
97
98 /**
99 * Turn xSpeed's page cache on, for a host acting on a user's request.
100 *
101 * Null means the cache is SERVING. A string is the reason it is not,
102 * already worded and translated — render it, do not re-interpret it. The
103 * refusal is not an error: installing beside another cache plugin and being
104 * told so is the designed outcome, and the site is fine.
105 *
106 * Two ways to not be serving, and only one of them is a refusal. On a host
107 * that ships wp-config.php read-only the drop-in installs and WP_CACHE
108 * cannot be written, so toggle() reports success with a line for the user
109 * to paste — and a caller reading only `blocked` announced a working cache
110 * that answers every request uncached. status()['page_cache_manual_snippet']
111 * carries that line.
112 *
113 * Only ever call this because a user asked. Activation deliberately leaves
114 * page caching off, and a host that turns it on unprompted is making a
115 * decision about shared WordPress state on the user's behalf.
116 */
117 public static function enable_page_cache(): ?string {
118 $state = Cache::toggle( true );
119
120 if ( ! empty( $state['blocked'] ) ) {
121 return is_string( $state['blocked_reason'] ) && '' !== $state['blocked_reason']
122 ? $state['blocked_reason']
123 : __( 'xSpeed could not enable the page cache on this site.', 'xspeed' );
124 }
125
126 if ( ! empty( $state['manual_snippet'] ) ) {
127 return sprintf(
128 /* translators: %s: the PHP line to add to wp-config.php. */
129 __( 'xSpeed installed its page cache, but wp-config.php is not writable. Add this line to it to start serving: %s', 'xspeed' ),
130 (string) $state['manual_snippet']
131 );
132 }
133
134 return null;
135 }
136
137 /** The slug of whoever installed xSpeed, or '' when the user did. */
138 public static function installed_by(): string {
139 return Settings::installed_by();
140 }
141
142 /**
143 * Everything a host needs to say whether the install came up right.
144 *
145 * One call, because the alternative is every consumer assembling the same
146 * four reads and drawing its own conclusion from them — which is how three
147 * plugins ended up with three different sentences for one situation.
148 *
149 * `profile` is how a FRESH install came up and is written once, at
150 * activation: `recommended` on a clear site, `conflict-safe` when
151 * something else owned the page cache, `''` when xSpeed was already
152 * installed and nothing was decided. It does not change afterwards — it
153 * records a decision, not the current settings.
154 *
155 * `page_cache_blocked_reason` is non-null only when the cache is NOT live
156 * and there is a nameable reason. A live cache and a clear field both give
157 * null, so it is safe to render whenever it is there.
158 *
159 * `page_cache_manual_snippet` is the wp-config.php line to paste, non-null
160 * only on a host that ships that file read-only. A cache that is not live
161 * and has no blocked reason is almost always this.
162 *
163 * @return array{
164 * installed:bool,
165 * active:bool,
166 * installed_by:string,
167 * profile:string,
168 * page_cache_live:bool,
169 * page_cache_owner:?string,
170 * page_cache_blocked_reason:?string,
171 * page_cache_manual_snippet:?string
172 * }
173 */
174 public static function status(): array {
175 $live = self::page_cache_is_live();
176
177 return array(
178 'installed' => self::is_installed(),
179 'active' => self::is_active(),
180 'installed_by' => self::installed_by(),
181 'profile' => Settings::install_profile(),
182 'page_cache_live' => $live,
183 'page_cache_owner' => self::page_cache_owner(),
184 'page_cache_blocked_reason' => $live ? null : Cache::acquisition_blocker(),
185 /*
186 * Not gated on `$live`. Serving and "there is a line to paste"
187 * are independent: on a managed host with an unwritable
188 * wp-config.php the cache serves every hit from
189 * template_redirect while WP_CACHE never landed, so the pre-boot
190 * path is still missing and the user can still act on it. The
191 * helper returns null whenever nothing is needed.
192 */
193 'page_cache_manual_snippet' => Cache::manual_wp_cache_snippet(),
194 );
195 }
196
197 /**
198 * Pull in get_plugins()/is_plugin_active(), which are admin-only.
199 *
200 * A host can ask these from a front-end or REST request, where the file is
201 * not loaded; and the callers above degrade rather than fatal if it is not
202 * there at all, because a missing answer must not take the site down.
203 */
204 private static function load_plugin_functions(): void {
205 if ( function_exists( 'get_plugins' ) && function_exists( 'is_plugin_active' ) ) {
206 return;
207 }
208 $file = ABSPATH . 'wp-admin/includes/plugin.php';
209 if ( file_exists( $file ) ) {
210 require_once $file;
211 }
212 }
213
214 /** How xSpeed appears in the plugin list. */
215 private const PLUGIN_FILE = 'xspeed/xspeed.php';
216 }
217