PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.1
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 1.1.2 All 35 releases
← All changes | includes/modules/ObjectCache/ObjectCacheModule.php +83 -28 1.3.5 → 1.4.1 View file →
@@ -19,8 +19,9 @@
19 19 defined( 'ABSPATH' ) || exit;
20 20
21 21 use XSpeed\Module;
22 22 use XSpeed\Object_Cache;
23 +use XSpeed\Object_Cache_Takeover;
23 24
24 25 final class ObjectCacheModule extends Module {
25 26
26 27 public const SLUG = 'object-cache';
@@ -30,9 +31,10 @@
30 31 public function ui_metadata(): array {
31 32 return array(
32 33 'label' => __( 'Object Cache', 'xspeed' ),
33 34 'icon' => 'Server',
34 - 'description' => __( 'Configure a persistent object cache (Redis / Memcached) and generate a paste-ready wp-config.php snippet.', 'xspeed' ),
35 + 'description' => __( 'Stores database query results in Redis or Memcached so pages build faster.', 'xspeed' ),
36 + 'group' => 'cache',
35 37 'custom_panel' => 'ObjectCachePanel',
36 38 );
37 39 }
38 40
@@ -57,17 +59,18 @@
57 59 'option_labels' => array(
58 60 'redis' => 'Redis',
59 61 'memcached' => 'Memcached',
60 62 ),
61 - 'label' => __( 'Backend', 'xspeed' ),
62 - 'description' => __( 'Which cache server you intend to use. Affects the generated wp-config snippet.', 'xspeed' ),
63 + 'label' => __( 'Cache server', 'xspeed' ),
64 + 'description' => __( 'The cache server your host provides. Ask your host if you are not sure.', 'xspeed' ),
63 65 ),
64 66 'redis_host' => array(
65 67 'type' => 'string',
66 68 'default' => '127.0.0.1',
67 69 'constants' => array( 'XSPEED_OC_HOST', 'WP_REDIS_HOST' ),
68 - 'label' => __( 'Redis Host', 'xspeed' ),
69 - 'description' => __( 'Hostname or IP of the Redis server. Use 127.0.0.1 for a local socket on the same machine as PHP.', 'xspeed' ),
70 + 'label' => __( 'Redis host', 'xspeed' ),
71 + 'description' => __( 'Address of the Redis server. Use 127.0.0.1 when Redis runs on the same server as your site.', 'xspeed' ),
72 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'redis' ),
70 73 ),
71 74 'redis_port' => array(
72 75 'type' => 'int',
73 76 'default' => 6379,
@@ -73,10 +76,12 @@
73 76 'default' => 6379,
74 77 'min' => 1,
75 78 'max' => 65535,
76 79 'constants' => array( 'XSPEED_OC_PORT', 'WP_REDIS_PORT' ),
77 - 'label' => __( 'Redis Port', 'xspeed' ),
78 - 'description' => __( 'Default Redis port is 6379.', 'xspeed' ),
80 + 'label' => __( 'Redis port', 'xspeed' ),
81 + 'description' => __( 'The default Redis port is 6379.', 'xspeed' ),
82 + 'advanced' => true,
83 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'redis' ),
79 84 ),
80 85 'redis_user' => array(
81 86 'type' => 'string',
82 87 'default' => '',
@@ -86,10 +91,11 @@
86 91 // as a plain string it is a password, never a username.
87 92 'constants' => array( 'XSPEED_OC_USER', 'WP_REDIS_USER', 'WP_REDIS_PASSWORD' ),
88 93 'constants_pair_only' => array( 'WP_REDIS_PASSWORD' ),
89 94 'constant_pair' => 'user',
90 - 'label' => __( 'Redis User', 'xspeed' ),
91 - '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).', 'xspeed' ),
95 + 'label' => __( 'Redis username', 'xspeed' ),
96 + 'description' => __( 'Only needed if your host gave you a Redis username along with the password. Leave blank otherwise.', 'xspeed' ),
97 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'redis' ),
92 98 ),
93 99 'redis_password' => array(
94 100 'type' => 'secret',
95 101 'default' => '',
@@ -94,10 +100,11 @@
94 100 'type' => 'secret',
95 101 'default' => '',
96 102 'constants' => array( 'XSPEED_OC_PASSWORD', 'WP_REDIS_PASSWORD' ),
97 103 'constant_pair' => 'password',
98 - 'label' => __( 'Redis Password', 'xspeed' ),
99 - 'description' => __( 'Leave blank if your Redis server runs without auth.', 'xspeed' ),
104 + 'label' => __( 'Redis password', 'xspeed' ),
105 + 'description' => __( 'Leave blank if your Redis server has no password.', 'xspeed' ),
106 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'redis' ),
100 107 ),
101 108 'redis_database' => array(
102 109 'type' => 'int',
103 110 'default' => 0,
@@ -103,10 +110,12 @@
103 110 'default' => 0,
104 111 'min' => 0,
105 112 'max' => 15,
106 113 'constants' => array( 'XSPEED_OC_DATABASE', 'WP_REDIS_DATABASE' ),
107 - 'label' => __( 'Redis Database', 'xspeed' ),
108 - 'description' => __( 'Redis logical DB number (0-15). Use a dedicated DB per site if Redis is shared.', 'xspeed' ),
114 + 'label' => __( 'Redis database', 'xspeed' ),
115 + 'description' => __( 'Database number, 0 to 15. If several sites share one Redis server, give each site its own number.', 'xspeed' ),
116 + 'advanced' => true,
117 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'redis' ),
109 118 ),
110 119 'memcached_host' => array(
111 120 'type' => 'string',
112 121 'default' => '127.0.0.1',
@@ -131,10 +140,11 @@
131 140 'var' => 'memcached_servers',
132 141 'reader' => array( '\\XSpeed\\Object_Cache', 'first_memcached_server' ),
133 142 'slot' => 0,
134 143 ),
135 - 'label' => __( 'Memcached Host', 'xspeed' ),
136 - 'description' => __( 'Used when Backend = Memcached.', 'xspeed' ),
144 + 'label' => __( 'Memcached host', 'xspeed' ),
145 + 'description' => __( 'Address of the Memcached server. Use 127.0.0.1 when it runs on the same server as your site.', 'xspeed' ),
146 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'memcached' ),
137 147 ),
138 148 'memcached_port' => array(
139 149 'type' => 'int',
140 150 'default' => 11211,
@@ -153,10 +163,12 @@
153 163 'var' => 'memcached_servers',
154 164 'reader' => array( '\\XSpeed\\Object_Cache', 'first_memcached_server' ),
155 165 'slot' => 1,
156 166 ),
157 - 'label' => __( 'Memcached Port', 'xspeed' ),
158 - 'description' => __( 'Default Memcached port is 11211.', 'xspeed' ),
167 + 'label' => __( 'Memcached port', 'xspeed' ),
168 + 'description' => __( 'The default Memcached port is 11211.', 'xspeed' ),
169 + 'advanced' => true,
170 + 'dependsOn' => array( 'field' => 'backend', 'value' => 'memcached' ),
159 171 ),
160 172 'key_prefix' => array(
161 173 'type' => 'string',
162 174 'default' => '',
@@ -180,10 +192,12 @@
180 192 // unlock the field. With no salt of our own the field stays
181 193 // blank and editable; the drop-in still honours a defined
182 194 // WP_CACHE_KEY_SALT as its last-resort salt at runtime.
183 195 'constants' => array( 'XSPEED_OC_SALT', 'WP_REDIS_PREFIX' ),
184 - 'label' => __( 'Cache Key Prefix', 'xspeed' ),
185 - 'description' => __( 'Unique salt for this site\'s cache keys. Leave blank and xSpeed derives one automatically for this install, so sites sharing a Redis/Memcached server never collide. On ACL/namespaced Redis (e.g. xCloud), set this to the host\'s "Redis Object Cache Key" — otherwise cache writes are denied (NOPERM) and nothing persists.', 'xspeed' ),
196 + 'label' => __( 'Cache key prefix', 'xspeed' ),
197 + 'description' => __( 'Leave blank and xSpeed picks a unique prefix for this site. Some managed Redis hosts, such as xCloud, give you a key to paste here, or nothing gets saved.', 'xspeed' ),
198 + // Not advanced: on ACL hosts it is required, and the write-denied
199 + // notice (Object_Cache::write_denied_message) sends users here.
186 200 ),
187 201 'connection_timeout' => array(
188 202 'type' => 'int',
189 203 'default' => 1,
@@ -189,18 +203,20 @@
189 203 'default' => 1,
190 204 'min' => 0,
191 205 'max' => 60,
192 206 'constants' => array( 'XSPEED_OC_TIMEOUT', 'WP_REDIS_TIMEOUT' ),
193 - 'label' => __( 'Connection Timeout (seconds)', 'xspeed' ),
207 + 'label' => __( 'Connection timeout (seconds)', 'xspeed' ),
194 208 'unit' => 'seconds',
195 - 'description' => __( 'How long to wait for a connection. Keep low (1-2s) so a misconfigured cache never stalls the page.', 'xspeed' ),
209 + 'description' => __( 'How long to wait for the cache server. Keep it at 1 or 2 seconds so a broken cache server never holds up a page.', 'xspeed' ),
210 + 'advanced' => true,
196 211 ),
197 212 'persistent' => array(
198 213 'type' => 'bool',
199 214 'default' => true,
200 215 'constants' => array( 'XSPEED_OC_PERSISTENT', 'WP_REDIS_PERSISTENT' ),
201 - 'label' => __( 'Persistent Connections', 'xspeed' ),
202 - 'description' => __( 'Reuse the connection across PHP requests when supported. Generally a win unless the cache server complains about idle connections.', 'xspeed' ),
216 + 'label' => __( 'Persistent connections', 'xspeed' ),
217 + 'description' => __( 'Keep the connection open between page loads. Turn off only if your cache server reports too many idle connections.', 'xspeed' ),
218 + 'advanced' => true,
203 219 ),
204 220 );
205 221 }
206 222
@@ -644,9 +660,9 @@
644 660 * probe recorded on save knows; until this it had no reader outside
645 661 * WP-CLI, so the panel kept saying "Redis ready" over a cache that
646 662 * stored nothing. (#398)
647 663 */
648 - $detect = Object_Cache::detect();
664 + $detect = Object_Cache::detect( true );
649 665 $failure = self::live_write_failure();
650 666 if ( null !== $failure ) {
651 667 $detect['write_probe'] = $failure;
652 668 }
@@ -733,12 +749,22 @@
733 749
734 750 public function rest_enable( \WP_REST_Request $request ) {
735 751 // Persist any settings sent with the enable call first, then act on them.
736 752 $body = $request->get_json_params();
753 + // `takeover` is an action, not a setting: take it out before the body
754 + // is saved as the settings row. Sent in the body rather than the query
755 + // string because on plain permalinks the REST base already carries
756 + // `?rest_route=`. (#686)
757 + $takeover = is_array( $body ) && ! empty( $body['takeover'] )
758 + ? rest_sanitize_boolean( $body['takeover'] )
759 + : rest_sanitize_boolean( $request->get_param( 'takeover' ) );
760 + if ( is_array( $body ) ) {
761 + unset( $body['takeover'] );
762 + }
737 763 if ( is_array( $body ) && ! empty( $body ) ) {
738 764 \XSpeed\Settings_Manager::update( self::SLUG, $body );
739 765 }
740 - $result = Object_Cache::enable( $this->get_settings() );
766 + $result = Object_Cache::enable( $this->get_settings(), array( 'takeover' => $takeover ) );
741 767
742 768 if ( $result['ok'] && class_exists( '\\XSpeed\\Activity_Log' ) ) {
743 769 \XSpeed\Activity_Log::record(
744 770 'object_cache_enabled',
@@ -749,9 +775,11 @@
749 775 return rest_ensure_response( $result );
750 776 }
751 777
752 778 public function rest_disable( \WP_REST_Request $request ) {
753 - $result = Object_Cache::disable();
779 + $result = Object_Cache::disable(
780 + array( 'restore' => rest_sanitize_boolean( $request->get_param( 'restore' ) ) )
781 + );
754 782 if ( $result['ok'] && class_exists( '\\XSpeed\\Activity_Log' ) ) {
755 783 \XSpeed\Activity_Log::record(
756 784 'object_cache_disabled',
757 785 'Object cache disabled.',
@@ -784,8 +812,20 @@
784 812 'type' => 'positional',
785 813 'name' => 'value',
786 814 'optional' => true,
787 815 ),
816 + array(
817 + 'type' => 'flag',
818 + 'name' => 'takeover',
819 + 'description' => 'With enable: switch from the plugin that owns object-cache.php (backs it up, turns that plugin off, installs xSpeed, undoes everything on failure).',
820 + 'optional' => true,
821 + ),
822 + array(
823 + 'type' => 'flag',
824 + 'name' => 'restore',
825 + 'description' => 'With disable: put back the plugin xSpeed switched from.',
826 + 'optional' => true,
827 + ),
788 828 ),
789 829 ),
790 830 );
791 831 }
@@ -941,13 +981,28 @@
941 981 $t = Object_Cache::test_connection( $this->get_settings() );
942 982 $t['ok'] ? \WP_CLI::success( $t['message'] ) : \WP_CLI::error( $t['message'] );
943 983 return;
944 984 case 'enable':
945 - $r = Object_Cache::enable( $this->get_settings() );
985 + $r = Object_Cache::enable(
986 + $this->get_settings(),
987 + array( 'takeover' => ! empty( $assoc['takeover'] ) )
988 + );
989 + if ( ! $r['ok'] && ! empty( $r['needs_takeover'] ) && Object_Cache_Takeover::STRATEGY_REFUSE !== ( $r['owner']['strategy'] ?? '' ) ) {
990 + // This handler also answers MCP (through Cli_Bridge), where
991 + // the switch is an argument, not a flag.
992 + \WP_CLI::error( $r['message'] . ' To switch, run again with --takeover (MCP: takeover: true).' );
993 + }
946 994 $r['ok'] ? \WP_CLI::success( $r['message'] ) : \WP_CLI::error( $r['message'] );
947 995 return;
948 996 case 'disable':
949 - $r = Object_Cache::disable();
997 + $r = Object_Cache::disable( array( 'restore' => ! empty( $assoc['restore'] ) ) );
998 + // Disabling worked; a restore that could not complete is a
999 + // warning, not a failed command.
1000 + if ( $r['ok'] && false === ( $r['restored'] ?? null ) ) {
1001 + \WP_CLI::warning( $r['message'] );
1002 + \WP_CLI::success( 'Object cache disabled.' );
1003 + return;
1004 + }
950 1005 $r['ok'] ? \WP_CLI::success( $r['message'] ) : \WP_CLI::error( $r['message'] );
951 1006 return;
952 1007 default:
953 1008 \WP_CLI::error( "Unknown action: $action" );