PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
← All changes | includes/base/cache.php +246 -69 1.3.101.4.1 View file →
@@ -2,8 +2,18 @@
2 2 namespace Dudlewebs\WPMCS;
3 3
4 4 defined('ABSPATH') || exit;
5 5
6 +/**
7 + * Plugin object cache wrapper.
8 + *
9 + * Supports all common WordPress environments on PHP 8.1+ and WordPress 5.9+:
10 + * 1. Default WordPress object cache (in-memory, per request — no Redis/Memcached plugin).
11 + * 2. Persistent object cache drop-ins (Redis Object Cache, Memcached, etc.).
12 + *
13 + * Reads and writes always go through wp_cache_* so behaviour is consistent;
14 + * flush strategy differs based on whether a persistent drop-in is active.
15 + */
6 16 class Cache {
7 17 private static $instance = null;
8 18 private $assets_url;
9 19 private $version;
@@ -59,25 +69,170 @@
59 69 return true;
60 70 }
61 71
62 72 /**
73 + * Whether a persistent object cache drop-in is active (Redis, Memcached, etc.).
74 + */
75 + private static function uses_persistent_object_cache() {
76 + return function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache();
77 + }
78 +
79 + /**
80 + * Build a consistent object cache key for get/set/delete.
81 + */
82 + private static function build_cache_key($key, $post_id, $meta_name, $meta_type = 'post') {
83 + $prefix = is_array($meta_type)
84 + ? (isset($meta_type['prefix']) ? $meta_type['prefix'] : 'custom')
85 + : $meta_type;
86 +
87 + return ('post' === $prefix ? '' : $prefix . '_')
88 + . ($post_id == false ? '' : $post_id . '_')
89 + . $meta_name
90 + . (!Utils::is_empty($key) ? '_' . $key : '');
91 + }
92 +
93 + /**
94 + * Read the raw meta array for a given storage backend.
95 + *
96 + * $meta_type is 'post' (default) or 'user' for WP's own dedicated helpers
97 + * (options table when $post_id is false); a plain string is passed straight
98 + * to WP's generic get_metadata() — correct for any standard WP object type
99 + * whose meta table uses WP's usual 'meta_id' primary key (post/user/comment/
100 + * term). Third-party meta tables that don't follow that convention (e.g.
101 + * BuddyBoss's groupmeta, which uses 'id') aren't safe to read/write via
102 + * get_metadata() directly — pass an array of ['get'=>, 'update'=>, 'delete'=>]
103 + * callables (each shaped like get_post_meta($id,$key,true)/update_post_meta(
104 + * $id,$key,$value)/delete_post_meta($id,$key)) so the caller's own,
105 + * already-correct meta functions are used instead.
106 + */
107 + private static function read_meta($post_id, $meta_name, $meta_type) {
108 + if (is_array($meta_type)) {
109 + // Callability is already verified by is_backend_available() before
110 + // any of get/set/delete_object_cache() reach this point.
111 + return call_user_func($meta_type['get'], $post_id, $meta_name, true);
112 + }
113 + if ('post' === $meta_type) {
114 + return $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
115 + }
116 + if ('user' === $meta_type) {
117 + return get_user_meta($post_id, $meta_name, true);
118 + }
119 + return get_metadata($meta_type, $post_id, $meta_name, true);
120 + }
121 +
122 + /**
123 + * Write the raw meta array for a given storage backend. See read_meta().
124 + */
125 + private static function write_meta($post_id, $meta_name, $meta_data, $meta_type) {
126 + if (is_array($meta_type)) {
127 + return call_user_func($meta_type['update'], $post_id, $meta_name, $meta_data);
128 + }
129 + if ('post' === $meta_type) {
130 + return $post_id == false ? update_option($meta_name, $meta_data) : update_post_meta($post_id, $meta_name, $meta_data);
131 + }
132 + if ('user' === $meta_type) {
133 + return update_user_meta($post_id, $meta_name, $meta_data);
134 + }
135 + return update_metadata($meta_type, $post_id, $meta_name, $meta_data);
136 + }
137 +
138 + /**
139 + * Whether $meta_type can actually be used right now. A custom backend's
140 + * callables may not be defined yet (e.g. the plugin/component that owns
141 + * them is currently inactive) — in that case the whole Cache layer must
142 + * be skipped entirely (no wp_cache_* writes either), so the caller falls
143 + * through to its own non-cached path exactly as it would for 'table' =>
144 + * false. Partially engaging the cache here (writing the intended value to
145 + * wp_cache_* while the real read/update/delete silently no-ops) would
146 + * leave a phantom entry that get/delete can never find to invalidate.
147 + */
148 + private static function is_backend_available($meta_type) {
149 + if (!is_array($meta_type)) {
150 + return true;
151 + }
152 + return isset($meta_type['get'], $meta_type['update'], $meta_type['delete'])
153 + && is_callable($meta_type['get'])
154 + && is_callable($meta_type['update'])
155 + && is_callable($meta_type['delete']);
156 + }
157 +
158 + /**
159 + * Delete the entire meta entry for a given storage backend. See read_meta().
160 + */
161 + private static function delete_meta_entirely($post_id, $meta_name, $meta_type) {
162 + if (is_array($meta_type)) {
163 + return call_user_func($meta_type['delete'], $post_id, $meta_name);
164 + }
165 + if ('post' === $meta_type) {
166 + return $post_id == false ? delete_option($meta_name) : delete_post_meta($post_id, $meta_name);
167 + }
168 + if ('user' === $meta_type) {
169 + return delete_user_meta($post_id, $meta_name);
170 + }
171 + return delete_metadata($meta_type, $post_id, $meta_name);
172 + }
173 +
174 + /**
175 + * Whether the active object cache supports a feature (WP 6.1+ or drop-in).
176 + */
177 + private static function cache_supports($feature) {
178 + return function_exists('wp_cache_supports') && wp_cache_supports($feature);
179 + }
180 +
181 + /**
182 + * Read from object cache with WP 5.5+ $found support.
183 + */
184 + private static function get_cached_value($cache_key, &$found) {
185 + $group = Schema::getConstant('CACHE_GROUP');
186 + $found = false;
187 +
188 + if (!function_exists('wp_cache_get')) {
189 + return false;
190 + }
191 +
192 + // wp_cache_get( ..., &$found ) is available from WordPress 5.5 (plugin requires 5.9).
193 + return wp_cache_get($cache_key, $group, false, $found);
194 + }
195 +
196 + /**
197 + * Write a value to the plugin object cache group.
198 + */
199 + private static function sync_object_cache($key, $data, $post_id, $meta_name, $cache_expire, $meta_type = 'post') {
200 + if (!function_exists('wp_cache_set') || !function_exists('wp_cache_delete')) {
201 + return;
202 + }
203 +
204 + $group = Schema::getConstant('CACHE_GROUP');
205 + $cache_key = self::build_cache_key($key, $post_id, $meta_name, $meta_type);
206 +
207 + wp_cache_delete($cache_key, $group);
208 + wp_cache_set($cache_key, $data, $group, $cache_expire);
209 + }
210 +
211 + /**
63 212 * Get Object Cache
213 + *
214 + * @param string $meta_type 'post' (default; uses options table when $post_id is false),
215 + * 'user', or any object type WP's generic get_metadata() recognizes
216 + * (e.g. 'group', registered by BuddyBoss).
64 217 */
65 - public static function get_object_cache($key = '', $post_id = false, $meta_name = false, $cache_expire = false, $is_user_meta = false) {
218 + public static function get_object_cache($key = '', $post_id = false, $meta_name = false, $cache_expire = false, $meta_type = 'post') {
219 + if (!self::is_backend_available($meta_type)) {
220 + return false;
221 + }
222 +
66 223 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
67 224 $cache_expire = $cache_expire == false ? Schema::getConstant('CACHE_EXPIRE') : $cache_expire;
225 + $cache_key = self::build_cache_key($key, $post_id, $meta_name, $meta_type);
226 + $found = false;
68 227
69 - $cache_key = ($is_user_meta ? 'user_' : '').($post_id == false ? '' : $post_id) . $meta_name . (!empty($key) ? '_'.$key : '');
228 + $data = self::get_cached_value($cache_key, $found);
229 + if ($found) {
230 + return $data;
231 + }
70 232
71 - $data = wp_cache_get($cache_key, Schema::getConstant('CACHE_GROUP'));
72 - if ($data !== false) return $data;
233 + $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
73 234
74 - if ($is_user_meta) {
75 - $meta_data = get_user_meta($post_id, $meta_name, true);
76 - } else {
77 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
78 - }
79 -
80 235 if( !Utils::is_empty($key) && is_array($meta_data) && array_key_exists($key, $meta_data) ) {
81 236 // If key exists in meta data, return it
82 237 $data = $meta_data[$key];
83 238 } elseif (is_array($meta_data) && Utils::is_empty($key)) {
@@ -87,10 +242,9 @@
87 242 // If key does not exist, return false
88 243 $data = false;
89 244 }
90 245
91 - // Set the data in object cache
92 - wp_cache_set($cache_key, $data, Schema::getConstant('CACHE_GROUP'), $cache_expire);
246 + self::sync_object_cache($key, $data, $post_id, $meta_name, $cache_expire, $meta_type);
93 247
94 248 return $data;
95 249 }
96 250
@@ -96,17 +250,17 @@
96 250
97 251 /**
98 252 * Set Object Cache
99 253 */
100 - public static function set_object_cache($key, $data, $post_id = false, $meta_name = false, $cache_expire = false, $is_user_meta = false) {
254 + public static function set_object_cache($key, $data, $post_id = false, $meta_name = false, $cache_expire = false, $meta_type = 'post') {
255 + if (!self::is_backend_available($meta_type)) {
256 + return false;
257 + }
258 +
101 259 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
102 260 $cache_expire = $cache_expire == false ? Schema::getConstant('CACHE_EXPIRE') : $cache_expire;
103 261
104 - if( $is_user_meta) {
105 - $meta_data = get_user_meta($post_id, $meta_name, true);
106 - } else {
107 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
108 - }
262 + $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
109 263
110 264 // If data is not an array, convert it to an array
111 265 if (!is_array($meta_data)) {
112 266 $meta_data = [];
@@ -114,76 +268,58 @@
114 268
115 269 if(Utils::is_empty($key)) {
116 270 // If key is empty, set the entire meta data
117 271 $meta_data = $data;
118 - } elseif (is_array($meta_data) && array_key_exists($key, $meta_data)) {
119 - if($meta_data[$key] == $data) {
120 - return true;
121 - }
122 - // If key exists, update the value
272 + $cache_value = $data;
273 + } else {
123 274 $meta_data[$key] = $data;
124 - } else {
125 - $meta_data[$key] = $data;
275 + $cache_value = $data;
126 276 }
127 277
128 278 // Update the meta data
129 - if ($is_user_meta) {
130 - $update_result = update_user_meta($post_id, $meta_name, $meta_data);
131 - } else {
132 - $update_result = $post_id == false ? update_option($meta_name, $meta_data) : update_post_meta($post_id, $meta_name, $meta_data);
279 + $update_result = self::write_meta($post_id, $meta_name, $meta_data, $meta_type);
280 +
281 + // Always sync object cache so Redis and default WP cache stay current.
282 + self::sync_object_cache($key, $cache_value, $post_id, $meta_name, $cache_expire, $meta_type);
283 +
284 + if ($update_result) {
285 + return true;
133 286 }
134 - // If update failed, return false
135 - if (!$update_result) {
136 - return false;
137 - }
138 287
139 - // Update the object cache if caching is enabled
140 - $cache_key = ($is_user_meta ? 'user_' : '') . ($post_id == false ? '' : $post_id) . $meta_name . '_' . $key;
141 - wp_cache_delete($cache_key, Schema::getConstant('CACHE_GROUP'));
142 - wp_cache_set($cache_key, $data, Schema::getConstant('CACHE_GROUP'), $cache_expire);
288 + // update_option/update_meta return false when the value is unchanged — treat as success once cache is synced.
289 + $stored = self::read_meta($post_id, $meta_name, $meta_type);
143 290
291 + return maybe_serialize($stored) === maybe_serialize($meta_data);
292 + }
144 293
145 - // Return success if data update was successful
146 - return $update_result;
147 - }
148 294
149 -
150 295
151 296 /**
152 297 * Delete object cache
153 298 */
154 - public static function delete_object_cache($key = false, $post_id = false, $meta_name = false, $is_user_meta = false) {
299 + public static function delete_object_cache($key = false, $post_id = false, $meta_name = false, $meta_type = 'post') {
300 + if (!self::is_backend_available($meta_type)) {
301 + return false;
302 + }
303 +
155 304 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
156 305 $update_result = false;
157 - if ($is_user_meta) {
158 - $meta_data = get_user_meta($post_id, $meta_name, true);
159 - if (is_array($meta_data) && array_key_exists($key, $meta_data)) {
160 - unset($meta_data[$key]);
161 - if (empty($meta_data)) {
162 - $update_result = delete_user_meta($post_id, $meta_name);
163 - } else {
164 - $update_result = update_user_meta($post_id, $meta_name, $meta_data);
165 - }
306 +
307 + $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
308 + if (is_array($meta_data) && array_key_exists($key, $meta_data)) {
309 + unset($meta_data[$key]);
310 + if (empty($meta_data)) {
311 + $update_result = self::delete_meta_entirely($post_id, $meta_name, $meta_type);
312 + } else {
313 + $update_result = self::write_meta($post_id, $meta_name, $meta_data, $meta_type);
166 314 }
167 - } else {
168 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
169 - if (is_array($meta_data) && array_key_exists($key, $meta_data)) {
170 - unset($meta_data[$key]);
171 - if (empty($meta_data)) {
172 - $update_result = $post_id == false ? delete_option($meta_name) : delete_post_meta($post_id, $meta_name);
173 - } else {
174 - $update_result = $post_id == false ? update_option($meta_name, $meta_data) : update_post_meta($post_id, $meta_name, $meta_data);
175 - }
176 - }
177 315 }
178 316
179 - // Delete from object cache
180 - if ($update_result && $key) {
181 - $cache_key = ($is_user_meta ? 'user_' : '') . ($post_id == false ? '' : $post_id) . $meta_name . '_' . $key;
317 + if ($update_result && !Utils::is_empty($key) && function_exists('wp_cache_delete')) {
318 + $cache_key = self::build_cache_key($key, $post_id, $meta_name, $meta_type);
182 319 wp_cache_delete($cache_key, Schema::getConstant('CACHE_GROUP'));
183 320 }
184 321
185 - // Return success if data delete was successful
186 322 return $update_result;
187 323 }
188 324
189 325
@@ -191,17 +327,58 @@
191 327 * Flush Cache
192 328 * @since 1.3.2
193 329 */
194 330 public static function flush_object_cache() {
195 - if(wp_cache_supports('flush_group')) {
196 - wp_cache_flush_group(Schema::getConstant('CACHE_GROUP'));
331 + $group = Schema::getConstant('CACHE_GROUP');
332 +
333 + if (self::uses_persistent_object_cache()) {
334 + // Redis / Memcached drop-in: flush only this plugin's cache group.
335 + self::flush_cache_group($group);
197 336 } else {
198 - wp_cache_flush();
337 + // Default WordPress in-memory cache: safe to flush the runtime cache.
338 + self::flush_runtime_cache();
199 339 }
340 +
200 341 self::$cached_data = [];
201 342 return true;
202 343 }
203 344
345 + /**
346 + * Flush the in-memory runtime cache (default WordPress, no drop-in plugin).
347 + */
348 + private static function flush_runtime_cache() {
349 + if (self::cache_supports('flush_runtime') && function_exists('wp_cache_flush_runtime')) {
350 + wp_cache_flush_runtime();
351 + return;
352 + }
353 +
354 + if (function_exists('wp_cache_flush')) {
355 + wp_cache_flush();
356 + }
357 + }
358 +
359 + /**
360 + * Flush all keys in the plugin cache group when the drop-in supports it.
361 + */
362 + private static function flush_cache_group($group) {
363 + if (!function_exists('wp_cache_flush_group')) {
364 + return false;
365 + }
366 +
367 + // Prefer the supported API so core never falls back to a full cache flush.
368 + if (self::cache_supports('flush_group')) {
369 + return (bool) wp_cache_flush_group($group);
370 + }
371 +
372 + // Persistent drop-ins (e.g. Redis Object Cache on WP 5.9) may provide flush_group
373 + // without wp_cache_supports() being available in core.
374 + if (self::uses_persistent_object_cache()) {
375 + return (bool) wp_cache_flush_group($group);
376 + }
377 +
378 + return false;
379 + }
380 +
204 381
205 382 /**
206 383 * Gets the instance of this class.
207 384 *
@@ -212,5 +389,5 @@
212 389 self::$instance = new self();
213 390 }
214 391 return self::$instance;
215 392 }
216 -}
393 +}