PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.1.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.1.2
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 / modules / ObjectCache / ObjectCacheModule.php

ObjectCacheModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.1.2, at includes/modules/ObjectCache/ObjectCacheModule.php

308 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Object Cache module — status, settings, wp-config snippet, flush.
4 *
5 * Tier: Free per FEATURES.md "Object Cache" §1-11 (LiteSpeed parity).
6 *
7 * We don't ship our own object-cache.php drop-in in this Free release
8 * (see Object_Cache class docblock for rationale). The settings here
9 * are surfaced via the wp-config snippet; advanced consumers (Pro,
10 * external drop-ins like Redis Object Cache) can read them too.
11 *
12 * @package XSpeed
13 */
14
15 declare(strict_types=1);
16
17 namespace XSpeed\Modules\ObjectCache;
18
19 defined( 'ABSPATH' ) || exit;
20
21 use XSpeed\Module;
22 use XSpeed\Object_Cache;
23
24 final class ObjectCacheModule extends Module {
25
26 public const SLUG = 'object-cache';
27 public const TIER = self::TIER_FREE;
28 public const VERSION = '1.0.0';
29
30 public function ui_metadata(): array {
31 return array(
32 'label' => 'Object Cache',
33 'icon' => 'Server',
34 'description' => 'Configure a persistent object cache (Redis / Memcached) and generate a paste-ready wp-config.php snippet.',
35 'custom_panel' => 'ObjectCachePanel',
36 );
37 }
38
39 public function settings_schema(): array {
40 return array(
41 'backend' => array(
42 'type' => 'enum',
43 'default' => 'redis',
44 'options' => array( 'redis', 'memcached' ),
45 'option_labels' => array(
46 'redis' => 'Redis',
47 'memcached' => 'Memcached',
48 ),
49 'label' => 'Backend',
50 'description' => 'Which cache server you intend to use. Affects the generated wp-config snippet.',
51 ),
52 'redis_host' => array(
53 'type' => 'string',
54 'default' => '127.0.0.1',
55 'label' => 'Redis Host',
56 'description' => 'Hostname or IP of the Redis server. Use 127.0.0.1 for a local socket on the same machine as PHP.',
57 ),
58 'redis_port' => array(
59 'type' => 'int',
60 'default' => 6379,
61 'min' => 1,
62 'max' => 65535,
63 'label' => 'Redis Port',
64 'description' => 'Default Redis port is 6379.',
65 ),
66 'redis_user' => array(
67 'type' => 'string',
68 'default' => '',
69 'label' => 'Redis User',
70 'description' => 'Optional. Set this only if your host provisioned a dedicated Redis ACL user (Redis 6+) — e.g. some managed hosts issue a Redis User alongside the password. Leave blank to authenticate as the default user (legacy password-only Redis).',
71 ),
72 'redis_password' => array(
73 'type' => 'string',
74 'default' => '',
75 'label' => 'Redis Password',
76 'description' => 'Leave blank if your Redis server runs without auth.',
77 ),
78 'redis_database' => array(
79 'type' => 'int',
80 'default' => 0,
81 'min' => 0,
82 'max' => 15,
83 'label' => 'Redis Database',
84 'description' => 'Redis logical DB number (0-15). Use a dedicated DB per site if Redis is shared.',
85 ),
86 'memcached_host' => array(
87 'type' => 'string',
88 'default' => '127.0.0.1',
89 'label' => 'Memcached Host',
90 'description' => 'Used when Backend = Memcached.',
91 ),
92 'memcached_port' => array(
93 'type' => 'int',
94 'default' => 11211,
95 'min' => 1,
96 'max' => 65535,
97 'label' => 'Memcached Port',
98 'description' => 'Default Memcached port is 11211.',
99 ),
100 'key_prefix' => array(
101 'type' => 'string',
102 'default' => '',
103 'label' => 'Cache Key Prefix',
104 'description' => 'Unique salt for this site\'s cache keys. Critical when multiple WP sites share one Redis/Memcached server. On ACL/namespaced Redis (e.g. xCloud), this MUST match the host\'s "Redis Object Cache Key" — otherwise cache writes are denied (NOPERM) and nothing persists.',
105 ),
106 'connection_timeout' => array(
107 'type' => 'int',
108 'default' => 1,
109 'min' => 0,
110 'max' => 60,
111 'label' => 'Connection Timeout (seconds)',
112 'description' => 'How long to wait for a connection. Keep low (1-2s) so a misconfigured cache never stalls the page.',
113 ),
114 'persistent' => array(
115 'type' => 'bool',
116 'default' => true,
117 'label' => 'Persistent Connections',
118 'description' => 'Reuse the connection across PHP requests when supported. Generally a win unless the cache server complains about idle connections.',
119 ),
120 );
121 }
122
123 public function boot(): void {
124 // Keep the deployed drop-in in sync with the shipped template. It is
125 // copied into wp-content/object-cache.php on enable and then never
126 // touched again — so a fix shipped in a plugin update (e.g. the
127 // stale-alloptions eviction on failed backend writes, issue #41)
128 // would never reach existing installs. Version-gated so the file
129 // comparison runs once per plugin version, not on every admin load.
130 add_action(
131 'admin_init',
132 static function (): void {
133 if ( get_option( 'xspeed_oc_dropin_synced', '' ) === XSPEED_VERSION ) {
134 return;
135 }
136 if ( Object_Cache::is_our_dropin_present() ) {
137 Object_Cache::install_dropin();
138 }
139 update_option( 'xspeed_oc_dropin_synced', XSPEED_VERSION );
140 }
141 );
142 }
143
144 public function rest_routes(): array {
145 $default = parent::rest_routes();
146 return array_merge(
147 $default,
148 array(
149 array(
150 'path' => '/detect',
151 'methods' => 'GET',
152 'callback' => array( $this, 'rest_detect' ),
153 ),
154 array(
155 'path' => '/flush',
156 'methods' => 'POST',
157 'callback' => array( $this, 'rest_flush' ),
158 ),
159 array(
160 'path' => '/snippet',
161 'methods' => 'GET',
162 'callback' => array( $this, 'rest_snippet' ),
163 ),
164 array(
165 'path' => '/test-connection',
166 'methods' => 'POST',
167 'callback' => array( $this, 'rest_test_connection' ),
168 ),
169 array(
170 'path' => '/enable',
171 'methods' => 'POST',
172 'callback' => array( $this, 'rest_enable' ),
173 ),
174 array(
175 'path' => '/disable',
176 'methods' => 'POST',
177 'callback' => array( $this, 'rest_disable' ),
178 ),
179 )
180 );
181 }
182
183 public function rest_detect( \WP_REST_Request $request ) {
184 return rest_ensure_response( Object_Cache::detect() );
185 }
186
187 public function rest_flush( \WP_REST_Request $request ) {
188 $ok = Object_Cache::flush();
189 if ( $ok && class_exists( '\\XSpeed\\Activity_Log' ) ) {
190 \XSpeed\Activity_Log::record(
191 'object_cache_flushed',
192 'Object cache flushed.',
193 \XSpeed\Activity_Log::INFO
194 );
195 }
196 return rest_ensure_response( array( 'ok' => $ok ) );
197 }
198
199 public function rest_snippet( \WP_REST_Request $request ) {
200 return rest_ensure_response(
201 array( 'snippet' => Object_Cache::render_config_snippet( $this->get_settings() ) )
202 );
203 }
204
205 /**
206 * Merge any settings sent in the request body over the saved settings, so
207 * the UI can "Test connection" with unsaved values. Only known keys pass.
208 */
209 private function settings_with_overrides( \WP_REST_Request $request ): array {
210 $settings = $this->get_settings();
211 $body = $request->get_json_params();
212 if ( is_array( $body ) ) {
213 foreach ( $settings as $key => $value ) {
214 if ( array_key_exists( $key, $body ) ) {
215 $settings[ $key ] = $body[ $key ];
216 }
217 }
218 }
219 return $settings;
220 }
221
222 public function rest_test_connection( \WP_REST_Request $request ) {
223 return rest_ensure_response( Object_Cache::test_connection( $this->settings_with_overrides( $request ) ) );
224 }
225
226 public function rest_enable( \WP_REST_Request $request ) {
227 // Persist any settings sent with the enable call first, then act on them.
228 $body = $request->get_json_params();
229 if ( is_array( $body ) && ! empty( $body ) ) {
230 \XSpeed\Settings_Manager::update( self::SLUG, $body );
231 }
232 $result = Object_Cache::enable( $this->get_settings() );
233
234 if ( $result['ok'] && class_exists( '\\XSpeed\\Activity_Log' ) ) {
235 \XSpeed\Activity_Log::record(
236 'object_cache_enabled',
237 'Object cache enabled (' . ( $result['test']['backend'] ?? '' ) . ').',
238 \XSpeed\Activity_Log::INFO
239 );
240 }
241 return rest_ensure_response( $result );
242 }
243
244 public function rest_disable( \WP_REST_Request $request ) {
245 $result = Object_Cache::disable();
246 if ( $result['ok'] && class_exists( '\\XSpeed\\Activity_Log' ) ) {
247 \XSpeed\Activity_Log::record(
248 'object_cache_disabled',
249 'Object cache disabled.',
250 \XSpeed\Activity_Log::INFO
251 );
252 }
253 return rest_ensure_response( $result );
254 }
255
256 public function cli_commands(): array {
257 return array(
258 array(
259 'name' => 'xspeed objcache',
260 'callback' => array( $this, 'cli_handler' ),
261 'shortdesc' => 'Show object cache status, flush, or print the wp-config snippet.',
262 'synopsis' => array(
263 array(
264 'type' => 'positional',
265 'name' => 'action',
266 'options' => array( 'status', 'flush', 'snippet', 'enable', 'disable', 'test' ),
267 'optional' => true,
268 ),
269 ),
270 ),
271 );
272 }
273
274 public function cli_handler( array $args, array $assoc ): void {
275 $action = $args[0] ?? 'status';
276 switch ( $action ) {
277 case 'status':
278 $d = Object_Cache::detect();
279 \WP_CLI::log( 'drop-in installed: ' . ( $d['drop_in_installed'] ? 'yes' : 'no' ) );
280 \WP_CLI::log( 'label: ' . $d['drop_in_label'] );
281 \WP_CLI::log( 'backend: ' . $d['backend'] );
282 \WP_CLI::log( 'ext object cache: ' . ( $d['wp_cache_active'] ? 'yes' : 'no' ) );
283 return;
284 case 'flush':
285 $ok = Object_Cache::flush();
286 $ok ? \WP_CLI::success( 'Flushed.' ) : \WP_CLI::error( 'Flush failed.' );
287 return;
288 case 'snippet':
289 \WP_CLI::log( Object_Cache::render_config_snippet( $this->get_settings() ) );
290 return;
291 case 'test':
292 $t = Object_Cache::test_connection( $this->get_settings() );
293 $t['ok'] ? \WP_CLI::success( $t['message'] ) : \WP_CLI::error( $t['message'] );
294 return;
295 case 'enable':
296 $r = Object_Cache::enable( $this->get_settings() );
297 $r['ok'] ? \WP_CLI::success( $r['message'] ) : \WP_CLI::error( $r['message'] );
298 return;
299 case 'disable':
300 $r = Object_Cache::disable();
301 $r['ok'] ? \WP_CLI::success( $r['message'] ) : \WP_CLI::error( $r['message'] );
302 return;
303 default:
304 \WP_CLI::error( "Unknown action: $action" );
305 }
306 }
307 }
308