PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.3
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-object-cache.php

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

173 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Object_Cache — read-only detector + flusher + wp-config snippet
4 * generator for the persistent object cache.
5 *
6 * We deliberately do NOT install our own object-cache.php drop-in
7 * from this Free release — that's invasive, has many failure modes
8 * (auth, TLS, cluster vs single, Redis vs Predis vs phpredis,
9 * Memcache vs Memcached), and changes how every site reads/writes
10 * persistent state. The Free plugin's role is:
11 *
12 * 1. Tell the user whether a drop-in is currently active and
13 * which backend it appears to be.
14 * 2. Provide a Flush button that calls wp_cache_flush() — which
15 * works regardless of which drop-in is installed.
16 * 3. Save backend-config values (host, port, password, etc.) and
17 * render a wp-config.php snippet the user can paste, so the
18 * flow is "configure here → copy snippet → install drop-in"
19 * without us writing to wp-config ourselves.
20 *
21 * The Pro plugin (or a later Free release once well tested) can
22 * ship a drop-in that consumes these saved values automatically.
23 *
24 * @package XSpeed
25 */
26
27 declare(strict_types=1);
28
29 namespace XSpeed;
30
31 defined( 'ABSPATH' ) || exit;
32
33 final class Object_Cache {
34
35 /**
36 * Inspect the runtime + filesystem for a persistent object cache.
37 *
38 * @return array{
39 * drop_in_installed: bool,
40 * drop_in_path: string,
41 * drop_in_label: string,
42 * backend: string, // redis|memcached|apcu|wp_default|unknown
43 * wp_cache_active: bool, // wp_using_ext_object_cache
44 * class_available: array<string,bool>
45 * }
46 */
47 public static function detect(): array {
48 $dropin = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/object-cache.php' : '';
49 $has_drop_in = '' !== $dropin && file_exists( $dropin );
50 $label = $has_drop_in ? self::sniff_drop_in_label( $dropin ) : '';
51 $ext_in_use = function_exists( 'wp_using_ext_object_cache' ) ? (bool) wp_using_ext_object_cache() : false;
52
53 // Class sniffer — independent of any plugin. Tells us what's
54 // available to actually use, separate from what's wired up.
55 $class_available = array(
56 'Redis' => class_exists( '\\Redis' ),
57 'Memcached' => class_exists( '\\Memcached' ),
58 'Memcache' => class_exists( '\\Memcache' ),
59 'APCu' => function_exists( 'apcu_enabled' ) && @apcu_enabled(),
60 );
61
62 $backend = 'unknown';
63 if ( ! $ext_in_use ) {
64 $backend = 'wp_default';
65 } elseif ( $has_drop_in ) {
66 // Best-effort: the label usually contains the backend name.
67 $lc = strtolower( $label );
68 if ( false !== strpos( $lc, 'redis' ) ) {
69 $backend = 'redis';
70 } elseif ( false !== strpos( $lc, 'memcached' ) || false !== strpos( $lc, 'memcache' ) ) {
71 $backend = 'memcached';
72 } elseif ( false !== strpos( $lc, 'apcu' ) ) {
73 $backend = 'apcu';
74 }
75 }
76
77 return array(
78 'drop_in_installed' => $has_drop_in,
79 'drop_in_path' => $dropin,
80 'drop_in_label' => $label,
81 'backend' => $backend,
82 'wp_cache_active' => $ext_in_use,
83 'class_available' => $class_available,
84 );
85 }
86
87 /**
88 * Flush whatever cache backend is wired up. Works against any
89 * compliant drop-in OR the WP default in-memory cache.
90 */
91 public static function flush(): bool {
92 if ( ! function_exists( 'wp_cache_flush' ) ) {
93 return false;
94 }
95 return (bool) wp_cache_flush();
96 }
97
98 /**
99 * Render a paste-into-wp-config.php snippet for the chosen backend
100 * using the supplied settings. The constant names match the
101 * conventions of the widely-used Redis Object Cache + W3TC drop-ins
102 * so users with those installed get a working configuration
103 * without any further translation.
104 */
105 public static function render_config_snippet( array $opts ): string {
106 $backend = (string) ( $opts['backend'] ?? 'redis' );
107 $lines = array( "/* xSpeed object cache config — paste above the \"That's all, stop editing!\" comment in wp-config.php. */" );
108
109 if ( 'redis' === $backend ) {
110 $host = self::str( $opts, 'redis_host', '127.0.0.1' );
111 $port = self::int( $opts, 'redis_port', 6379 );
112 $pass = self::str( $opts, 'redis_password', '' );
113 $db = self::int( $opts, 'redis_database', 0 );
114 $prefix = self::str( $opts, 'key_prefix', '' );
115 $timeout = self::int( $opts, 'connection_timeout', 1 );
116 $persist = ! empty( $opts['persistent'] );
117
118 $lines[] = "define( 'WP_REDIS_HOST', '" . self::esc( $host ) . "' );";
119 $lines[] = "define( 'WP_REDIS_PORT', " . $port . ' );';
120 if ( '' !== $pass ) {
121 $lines[] = "define( 'WP_REDIS_PASSWORD', '" . self::esc( $pass ) . "' );";
122 }
123 $lines[] = "define( 'WP_REDIS_DATABASE', " . $db . ' );';
124 if ( '' !== $prefix ) {
125 $lines[] = "define( 'WP_CACHE_KEY_SALT', '" . self::esc( $prefix ) . "' );";
126 }
127 $lines[] = "define( 'WP_REDIS_TIMEOUT', " . $timeout . ' );';
128 $lines[] = "define( 'WP_REDIS_PERSISTENT', " . ( $persist ? 'true' : 'false' ) . ' );';
129 } elseif ( 'memcached' === $backend ) {
130 $host = self::str( $opts, 'memcached_host', '127.0.0.1' );
131 $port = self::int( $opts, 'memcached_port', 11211 );
132 $prefix = self::str( $opts, 'key_prefix', '' );
133 $lines[] = "global \$memcached_servers;";
134 $lines[] = "\$memcached_servers = array( array( '" . self::esc( $host ) . "', " . $port . ' ) );';
135 if ( '' !== $prefix ) {
136 $lines[] = "define( 'WP_CACHE_KEY_SALT', '" . self::esc( $prefix ) . "' );";
137 }
138 } else {
139 $lines[] = '// No snippet for backend: ' . $backend;
140 }
141
142 return implode( "\n", $lines ) . "\n";
143 }
144
145 private static function sniff_drop_in_label( string $path ): string {
146 $head = @file_get_contents( $path, false, null, 0, 2048 );
147 if ( ! is_string( $head ) || '' === $head ) {
148 return '';
149 }
150 // PluginName / Plugin Name in standard WP file header form.
151 if ( preg_match( '#Plugin Name:\s*([^\r\n]+)#i', $head, $m ) ) {
152 return trim( $m[1] );
153 }
154 // Many drop-ins just put their identity in a comment.
155 if ( preg_match( '#\*\s*([A-Za-z][A-Za-z0-9 _\-]{2,40}(?:Cache|Redis|Memcached)[^\r\n]*)#i', $head, $m ) ) {
156 return trim( $m[1] );
157 }
158 return basename( $path );
159 }
160
161 private static function str( array $opts, string $key, string $default ): string {
162 return isset( $opts[ $key ] ) && '' !== $opts[ $key ] ? (string) $opts[ $key ] : $default;
163 }
164
165 private static function int( array $opts, string $key, int $default ): int {
166 return isset( $opts[ $key ] ) && '' !== $opts[ $key ] ? (int) $opts[ $key ] : $default;
167 }
168
169 private static function esc( string $s ): string {
170 return str_replace( array( '\\', "'" ), array( '\\\\', "\\'" ), $s );
171 }
172 }
173