PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.4
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.4
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 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 All 30 releases
← All changes | includes/class-redis-client.php +58 -1 1.0.41.3.4 View file →
@@ -65,8 +65,19 @@
65 65 $this->timeout,
66 66 $flags
67 67 );
68 68 if ( ! $sock ) {
69 + // The `@` above hides the warning from output/logs, but a failed
70 + // connect (e.g. an unreachable/unresolvable host) still leaves a
71 + // PHP warning in `error_get_last()`. WP reads that at
72 + // `admin_body_class` time and tags the page `php-error` — which
73 + // renders an empty banner above the admin menu even though we
74 + // handle the failure gracefully (caller falls back to a
75 + // non-persistent cache). Clear it so a degraded-but-handled Redis
76 + // backend doesn't masquerade as a site error.
77 + if ( function_exists( 'error_clear_last' ) ) {
78 + error_clear_last();
79 + }
69 80 return false;
70 81 }
71 82 stream_set_timeout( $sock, (int) $this->timeout, (int) ( ( $this->timeout - (int) $this->timeout ) * 1000000 ) );
72 83 $this->sock = $sock;
@@ -78,9 +89,23 @@
78 89 }
79 90
80 91 // --- Commands -----------------------------------------------------------
81 92
82 - public function auth( string $password ) {
93 + /**
94 + * Authenticate. With a username (Redis 6+ ACL) emit the two-argument
95 + * `AUTH <user> <pass>`; without one fall back to the legacy
96 + * single-argument `AUTH <pass>` (which authenticates as the built-in
97 + * `default` user). Managed hosts that provision a dedicated ACL user
98 + * and disable `default` reject the one-arg form with WRONGPASS, so the
99 + * username must be threaded through here. (FBS-83118)
100 + *
101 + * @param string $password Redis password.
102 + * @param string $username Optional ACL username; '' = legacy default user.
103 + */
104 + public function auth( string $password, string $username = '' ) {
105 + if ( '' !== $username ) {
106 + return $this->command( array( 'AUTH', $username, $password ) );
107 + }
83 108 return $this->command( array( 'AUTH', $password ) );
84 109 }
85 110
86 111 public function select( int $db ) {
@@ -144,8 +169,40 @@
144 169
145 170 public function flushDB(): bool {
146 171 $r = $this->command( array( 'FLUSHDB' ) );
147 172 return '+OK' === $r || 'OK' === $r;
173 + }
174 +
175 + /**
176 + * Delete every key matching a glob-style pattern, using a non-blocking
177 + * SCAN cursor (never KEYS, which blocks the whole server on large
178 + * datasets). Returns the number of keys deleted. Used for prefix-scoped
179 + * flushes so we only ever touch this site's namespace, never the whole
180 + * Redis DB. (FBS-83119)
181 + *
182 + * @param string $pattern e.g. "salt:*" or "salt:*:options:*".
183 + * @param int $count SCAN COUNT hint (batch size per round trip).
184 + */
185 + public function delete_by_pattern( string $pattern, int $count = 500 ): int {
186 + $deleted = 0;
187 + $cursor = '0';
188 + do {
189 + $reply = $this->command( array( 'SCAN', $cursor, 'MATCH', $pattern, 'COUNT', (string) $count ) );
190 + // Expected: [ next_cursor, [ key, key, ... ] ]. Anything else
191 + // (false on socket error, malformed) ends the loop safely.
192 + if ( ! is_array( $reply ) || count( $reply ) < 2 || ! is_array( $reply[1] ) ) {
193 + break;
194 + }
195 + $cursor = (string) $reply[0];
196 + $keys = $reply[1];
197 + if ( ! empty( $keys ) ) {
198 + // DEL accepts variadic keys — one round trip per batch.
199 + $args = array_merge( array( 'DEL' ), array_map( 'strval', $keys ) );
200 + $n = $this->command( $args );
201 + $deleted += is_int( $n ) ? $n : 0;
202 + }
203 + } while ( '0' !== $cursor );
204 + return $deleted;
148 205 }
149 206
150 207 public function close(): void {
151 208 if ( is_resource( $this->sock ) && ! $this->persistent ) {