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
media-cloud-sync / includes / base / cache.php

cache.php in Media Cloud Sync 1.4.1, at includes/base/cache.php

394 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Dudlewebs\WPMCS;
3
4 defined('ABSPATH') || exit;
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 */
16 class Cache {
17 private static $instance = null;
18 private $assets_url;
19 private $version;
20 private $token;
21
22
23 private static $cached_data=[];
24
25 /**
26 * Admin constructor.
27 * @since 1.0.0
28 */
29 public function __construct() {
30 $this->assets_url = WPMCS_ASSETS_URL;
31 $this->version = WPMCS_VERSION;
32 $this->token = WPMCS_TOKEN;
33
34 // Initialize setup
35 $this->init();
36 }
37
38 /**
39 * Initialize datas
40 */
41 public function init() {
42 }
43
44
45 /**
46 * Update Static Cache
47 */
48 public static function update_item_cache( $item_id, $data ){
49 self::$cached_data[$item_id] = $data;
50 return true;
51 }
52
53 /**
54 * Update Static Cache
55 */
56 public static function get_item_cache( $item_id, $default = false ){
57 return isset(self::$cached_data[$item_id]) ? self::$cached_data[$item_id] : $default;
58 }
59
60 /**
61 * Delete Static Cache
62 */
63 public static function delete_item_cache( $item_id = false ){
64 if($item_id === false) {
65 self::$cached_data = [];
66 } else {
67 unset(self::$cached_data[$item_id]);
68 }
69 return true;
70 }
71
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 /**
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).
217 */
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
223 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
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;
227
228 $data = self::get_cached_value($cache_key, $found);
229 if ($found) {
230 return $data;
231 }
232
233 $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
234
235 if( !Utils::is_empty($key) && is_array($meta_data) && array_key_exists($key, $meta_data) ) {
236 // If key exists in meta data, return it
237 $data = $meta_data[$key];
238 } elseif (is_array($meta_data) && Utils::is_empty($key)) {
239 // If no key is provided, return the entire meta data
240 $data = $meta_data;
241 } else {
242 // If key does not exist, return false
243 $data = false;
244 }
245
246 self::sync_object_cache($key, $data, $post_id, $meta_name, $cache_expire, $meta_type);
247
248 return $data;
249 }
250
251 /**
252 * Set Object Cache
253 */
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
259 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
260 $cache_expire = $cache_expire == false ? Schema::getConstant('CACHE_EXPIRE') : $cache_expire;
261
262 $meta_data = self::read_meta($post_id, $meta_name, $meta_type);
263
264 // If data is not an array, convert it to an array
265 if (!is_array($meta_data)) {
266 $meta_data = [];
267 }
268
269 if(Utils::is_empty($key)) {
270 // If key is empty, set the entire meta data
271 $meta_data = $data;
272 $cache_value = $data;
273 } else {
274 $meta_data[$key] = $data;
275 $cache_value = $data;
276 }
277
278 // Update the 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;
286 }
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);
290
291 return maybe_serialize($stored) === maybe_serialize($meta_data);
292 }
293
294
295
296 /**
297 * Delete object cache
298 */
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
304 $meta_name = $meta_name == false ? Schema::getConstant('META_KEY') : $meta_name;
305 $update_result = false;
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);
314 }
315 }
316
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);
319 wp_cache_delete($cache_key, Schema::getConstant('CACHE_GROUP'));
320 }
321
322 return $update_result;
323 }
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
381
382 /**
383 * Gets the instance of this class.
384 *
385 * @return Cache
386 */
387 public static function instance(){
388 if (is_null(self::$instance)) {
389 self::$instance = new self();
390 }
391 return self::$instance;
392 }
393 }
394