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 +257 -72 1.2.121.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,28 +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_enabled = wp_using_ext_object_cache();
70 - $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 + }
71 232
72 - if ($cache_enabled) {
73 - $data = wp_cache_get($cache_key, Schema::getConstant('CACHE_GROUP'));
74 - if ($data !== false) return $data;
75 - }
233 + $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
76 234
77 - if ($is_user_meta) {
78 - $meta_data = get_user_meta($post_id, $meta_name, true);
79 - } else {
80 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
81 - }
82 -
83 235 if( !Utils::is_empty($key) && is_array($meta_data) && array_key_exists($key, $meta_data) ) {
84 236 // If key exists in meta data, return it
85 237 $data = $meta_data[$key];
86 238 } elseif (is_array($meta_data) && Utils::is_empty($key)) {
@@ -90,11 +242,9 @@
90 242 // If key does not exist, return false
91 243 $data = false;
92 244 }
93 245
94 - if ($cache_enabled) {
95 - wp_cache_set($cache_key, $data, Schema::getConstant('CACHE_GROUP'), $cache_expire);
96 - }
246 + self::sync_object_cache($key, $data, $post_id, $meta_name, $cache_expire, $meta_type);
97 247
98 248 return $data;
99 249 }
100 250
@@ -100,17 +250,17 @@
100 250
101 251 /**
102 252 * Set Object Cache
103 253 */
104 - 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 +
105 259 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
106 260 $cache_expire = $cache_expire == false ? Schema::getConstant('CACHE_EXPIRE') : $cache_expire;
107 261
108 - if( $is_user_meta) {
109 - $meta_data = get_user_meta($post_id, $meta_name, true);
110 - } else {
111 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
112 - }
262 + $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
113 263
114 264 // If data is not an array, convert it to an array
115 265 if (!is_array($meta_data)) {
116 266 $meta_data = [];
@@ -118,82 +268,117 @@
118 268
119 269 if(Utils::is_empty($key)) {
120 270 // If key is empty, set the entire meta data
121 271 $meta_data = $data;
122 - } elseif (is_array($meta_data) && array_key_exists($key, $meta_data)) {
123 - if($meta_data[$key] == $data) {
124 - return true;
125 - }
126 - // If key exists, update the value
272 + $cache_value = $data;
273 + } else {
127 274 $meta_data[$key] = $data;
128 - } else {
129 - $meta_data[$key] = $data;
275 + $cache_value = $data;
130 276 }
131 277
132 278 // Update the meta data
133 - if ($is_user_meta) {
134 - $update_result = update_user_meta($post_id, $meta_name, $meta_data);
135 - } else {
136 - $update_result = $post_id == false ? update_option($meta_name, $meta_data) : update_post_meta($post_id, $meta_name, $meta_data);
137 - }
138 - // If update failed, return false
139 - if (!$update_result) {
140 - return false;
141 - }
279 + $update_result = self::write_meta($post_id, $meta_name, $meta_data, $meta_type);
142 280
143 - // If object cache is enabled, set the cache
144 - // and delete the old cache entry if it exists
145 - if (wp_using_ext_object_cache()) {
146 - wp_cache_delete($meta_name, Schema::getConstant('CACHE_GROUP'));
147 - $cache_key = ($is_user_meta ? 'user_' : '') . ($post_id == false ? '' : $post_id) . $meta_name . '_' . $key;
148 - wp_cache_set($cache_key, $data, Schema::getConstant('CACHE_GROUP'), $cache_expire);
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;
149 286 }
150 287
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);
151 290
152 - // Return success if data update was successful
153 - return $update_result;
291 + return maybe_serialize($stored) === maybe_serialize($meta_data);
154 292 }
155 293
156 -
157 294
295 +
158 296 /**
159 297 * Delete object cache
160 298 */
161 - 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 +
162 304 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
163 305 $update_result = false;
164 - if ($is_user_meta) {
165 - $meta_data = get_user_meta($post_id, $meta_name, true);
166 - if (is_array($meta_data) && array_key_exists($key, $meta_data)) {
167 - unset($meta_data[$key]);
168 - if (empty($meta_data)) {
169 - $update_result = delete_user_meta($post_id, $meta_name);
170 - } else {
171 - $update_result = update_user_meta($post_id, $meta_name, $meta_data);
172 - }
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);
173 314 }
174 - } else {
175 - $meta_data = $post_id == false ? get_option($meta_name, []) : get_post_meta($post_id, $meta_name, true);
176 - if (is_array($meta_data) && array_key_exists($key, $meta_data)) {
177 - unset($meta_data[$key]);
178 - if (empty($meta_data)) {
179 - $update_result = $post_id == false ? delete_option($meta_name) : delete_post_meta($post_id, $meta_name);
180 - } else {
181 - $update_result = $post_id == false ? update_option($meta_name, $meta_data) : update_post_meta($post_id, $meta_name, $meta_data);
182 - }
183 - }
184 315 }
185 316
186 - // Delete from object cache if caching is enabled
187 - if (wp_using_ext_object_cache() && $update_result && $key) {
188 - $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);
189 319 wp_cache_delete($cache_key, Schema::getConstant('CACHE_GROUP'));
190 320 }
191 321
192 - // Return success if data delete was successful
193 322 return $update_result;
194 323 }
195 324
325 +
326 + /**
327 + * Flush Cache
328 + * @since 1.3.2
329 + */
330 + public static function flush_object_cache() {
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);
336 + } else {
337 + // Default WordPress in-memory cache: safe to flush the runtime cache.
338 + self::flush_runtime_cache();
339 + }
340 +
341 + self::$cached_data = [];
342 + return true;
343 + }
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 +
196 381
197 382 /**
198 383 * Gets the instance of this class.
199 384 *
@@ -204,5 +389,5 @@
204 389 self::$instance = new self();
205 390 }
206 391 return self::$instance;
207 392 }
208 -}
393 +}