PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / trunk
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More vtrunk
2.8.0 2.7.0 2.6.7 2.6.8 2.6.5 2.6.4 2.6.3 2.6.2 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2.0 2.0 2.1.0 2.1.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1
reviews-feed / class / Common / FeedCache.php
reviews-feed / class / Common Last commit date
Admin 1 week ago Builder 13 hours ago Customizer 1 week ago Exceptions 5 months ago Helpers 1 month ago Integrations 1 week ago Migrations 3 years ago ReviewAlerts 1 week ago Services 3 weeks ago Settings 2 months ago Support 2 months ago Traits 5 months ago Utils 3 weeks ago AuthorizationStatusCheck.php 1 week ago BusinessDataCache.php 5 months ago Clear_Cache.php 2 months ago Container.php 5 months ago DisplayElements.php 1 month ago Email_Notification.php 5 months ago Error_Reporter.php 2 months ago Feed.php 2 weeks ago FeedCache.php 4 months ago FeedCacheUpdater.php 2 months ago FeedDisplay.php 1 week ago Feed_Locator.php 5 months ago Parser.php 3 weeks ago PostAggregator.php 3 weeks ago RemoteRequest.php 1 week ago SBR_Education.php 2 months ago SBR_Settings.php 5 months ago ServiceContainer.php 2 weeks ago SinglePostCache.php 2 weeks ago TemplateRenderer.php 5 months ago Tooltip_Wizard.php 2 months ago Util.php 13 hours ago
FeedCache.php
623 lines
1 <?php
2
3 /**
4 * Class FeedCache
5 *
6 * @since 1.0
7 */
8
9 namespace SmashBalloon\Reviews\Common;
10
11 class FeedCache {
12 public const CACHES_TABLE_NAME = 'sbr_feed_caches';
13
14 public const CACHE_KEY = 'sbr_feed_';
15
16 /**
17 * @var string
18 */
19 protected $feed_id;
20
21 /**
22 * @var string
23 */
24 protected $suffix;
25
26 /**
27 * @var int
28 */
29 private $cache_time;
30
31 /**
32 * @var bool
33 */
34 private $is_expired = true;
35
36 /**
37 * @var array
38 */
39 private $posts;
40
41 /**
42 * @var array
43 */
44 private $header;
45
46 /**
47 * @var array
48 */
49 private $posts_backup;
50
51 /**
52 * @var array
53 */
54 private $header_backup;
55
56 /**
57 * @var array
58 */
59 private $errors;
60
61
62
63 /**
64 * SBI_Cache constructor. Set the feed id, cache key, legacy
65 *
66 * @param string $feed_id
67 * @param int $cache_time
68 *
69 * @since 6.0
70 */
71 public function __construct($feed_id, $cache_time = 0)
72 {
73 $this->cache_time = (int) $cache_time;
74 $this->feed_id = str_replace('*', '', $feed_id);
75 $this->suffix = '';
76
77 if (is_admin()) {
78 $this->feed_id .= $this->maybe_customizer_suffix();
79 }
80 }
81
82 /**
83 * Set all caches based on available data.
84 *
85 * @since 6.0
86 */
87 public function retrieve_and_set()
88 {
89 $expired = true;
90 $existing_caches = $this->query_feed_caches();
91
92 foreach ($existing_caches as $cache) {
93 switch ($cache['cache_key']) {
94 case 'posts':
95 $this->posts = $cache['cache_value'];
96 if (strtotime($cache['last_updated']) > time() - $this->cache_time) {
97 $expired = false;
98 }
99
100 if (empty($cache['cache_value'])) {
101 $expired = true;
102 }
103
104 break;
105 case 'header':
106 $this->header = $cache['cache_value'];
107 break;
108 case 'posts_backup' . $this->suffix:
109 $this->posts_backup = $cache['cache_value'];
110 break;
111 case 'header_backup' . $this->suffix:
112 $this->header_backup = $cache['cache_value'];
113 break;
114 case 'errors' . $this->suffix:
115 $this->errors = $cache['cache_value'];
116 break;
117 }
118 }
119
120 $this->is_expired = $expired;
121 if ($this->cache_time < 1) {
122 $this->is_expired = true;
123 }
124 }
125
126 /**
127 * Whether or not the cache needs to be refreshed
128 *
129 * @param string $cache_type
130 *
131 * @return bool
132 *
133 * @since 6.0
134 */
135 public function is_expired($cache_type = 'posts')
136 {
137 if ($cache_type !== 'posts') {
138 $cache = $this->get($cache_type);
139
140 return ( empty($cache) || $this->is_expired );
141 }
142
143 return $this->is_expired;
144 }
145
146 /**
147 * Get data currently stored in the database for the type
148 *
149 * @param string $type
150 *
151 * @return string
152 *
153 * @since 6.0
154 */
155 public function get($type)
156 {
157 $return = array();
158 switch ($type) {
159 case 'posts':
160 $return = $this->posts;
161 break;
162 case 'posts' . $this->suffix:
163 $return = $this->posts_page;
164 break;
165 case 'header':
166 $return = $this->header;
167 break;
168 case 'posts_backup':
169 $return = $this->posts_backup;
170 break;
171 case 'header_backup':
172 $return = $this->header_backup;
173 break;
174 case 'errors':
175 $return = $this->errors;
176 break;
177 }
178
179 return $return;
180 }
181
182 /**
183 * @param string $type
184 * @param array $cache_value
185 *
186 * @since 6.0
187 */
188 public function set($type, $cache_value)
189 {
190 switch ($type) {
191 case 'posts':
192 $this->posts = $cache_value;
193 break;
194 case 'posts' . $this->suffix:
195 $this->posts_page = $cache_value;
196 break;
197 case 'header':
198 $this->header = $cache_value;
199 break;
200 case 'posts_backup':
201 $this->posts_backup = $cache_value;
202 break;
203 case 'header_backup':
204 $this->header_backup = $cache_value;
205 break;
206 case 'errors':
207 $this->errors = $cache_value;
208 break;
209 }
210 }
211
212 /**
213 * Update a single cache with new data. Try to accept any data and convert it
214 * to JSON if needed
215 *
216 * @param string $cache_type
217 * @param array|object|string $cache_value
218 * @param bool $include_backup
219 * @param bool $cron_update
220 *
221 * @return int
222 *
223 * @since 6.0
224 */
225 public function update_or_insert($cache_type, $cache_value, $include_backup = true, $cron_update = true)
226 {
227 $this->clear_wp_cache();
228 if ($cache_type !== 'posts' && $cache_type !== 'header') {
229 $cron_update = false;
230 }
231
232 if (strpos($this->feed_id, '_CUSTOMIZER') !== false) {
233 $cron_update = false;
234 }
235
236 // When updating regular cache with posts, clear stale Customizer cache
237 // This prevents race condition where Customizer cached empty results before posts were fetched
238 if ($cache_type === 'posts' && strpos($this->feed_id, '_CUSTOMIZER') === false) {
239 // Handle both array and string cache values
240 $has_posts = false;
241 if (is_array($cache_value) || is_object($cache_value)) {
242 $has_posts = ! empty($cache_value);
243 } elseif (is_string($cache_value)) {
244 $has_posts = ! empty($cache_value) && $cache_value !== '[]' && $cache_value !== 'null';
245 }
246
247 if ($has_posts) {
248 $this->clear_customizer_posts_cache();
249 }
250 }
251
252 $cache_key = $cache_type . $this->suffix;
253
254 $this->set($cache_key, $cache_value);
255
256 if (is_array($cache_value) || is_object($cache_value)) {
257 $cache_value = wp_json_encode($cache_value);
258 }
259
260 global $wpdb;
261 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
262
263 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared -- Table name is safely constructed from $wpdb->prefix
264 $sql = $wpdb->prepare(
265 "
266 SELECT * FROM $cache_table_name
267 WHERE feed_id = %s
268 AND cache_key = %s",
269 $this->feed_id,
270 $cache_key
271 );
272
273 $existing = $wpdb->get_results($sql, ARRAY_A);
274 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
275 $data = array();
276 $where = array();
277 $format = array();
278
279 $data['cache_value'] = $cache_value;
280 $format[] = '%s';
281
282 $data['last_updated'] = date('Y-m-d H:i:s');
283 $format[] = '%s';
284
285 if (! empty($existing[0])) {
286 $where['feed_id'] = $this->feed_id;
287 $where_format[] = '%s';
288
289 $where['cache_key'] = $cache_key;
290 $where_format[] = '%s';
291
292 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
293 } else {
294 $data['cache_key'] = $cache_key;
295 $format[] = '%s';
296
297 $data['cron_update'] = $cron_update === true ? 'yes' : '';
298 $format[] = '%s';
299
300 $data['feed_id'] = $this->feed_id;
301 $format[] = '%s';
302
303 $affected = $wpdb->insert($cache_table_name, $data, $format);
304 }
305
306 return $affected;
307 }
308
309 /**
310 * Tasks to do after a new set of posts are retrieved
311 *
312 * @since 6.0
313 */
314 public function after_new_posts_retrieved()
315 {
316 $this->clear('all');
317 }
318
319 /**
320 * Resets caches after they expire
321 *
322 * @param string $type
323 *
324 * @return bool|false|int
325 *
326 * @since 6.0
327 */
328 public function clear($type)
329 {
330 $this->clear_wp_cache();
331
332 global $wpdb;
333 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
334
335 $feed_id = str_replace(array( '_CUSTOMIZER', '_CUSTOMIZER_MODMODE' ), '', $this->feed_id);
336
337 if ($type === 'all') {
338 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed from $wpdb->prefix
339 $affected = $wpdb->query(
340 $wpdb->prepare(
341 "UPDATE $cache_table_name
342 SET cache_value = ''
343 WHERE feed_id = %s
344 AND cache_key NOT IN ( 'posts', 'posts_backup', 'header_backup' );",
345 $feed_id
346 )
347 );
348
349 $affected = $wpdb->query(
350 $wpdb->prepare(
351 "UPDATE $cache_table_name
352 SET cache_value = ''
353 WHERE feed_id = %s",
354 $feed_id . '_CUSTOMIZER'
355 )
356 );
357 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
358
359 $mod_mode_where = esc_sql($feed_id) . '_CUSTOMIZER_MODMODE%';
360 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed from $wpdb->prefix
361 $affected = $wpdb->query(
362 $wpdb->prepare(
363 "UPDATE $cache_table_name
364 SET cache_value = ''
365 WHERE feed_id like %s",
366 $mod_mode_where
367 )
368 );
369 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
370 } else {
371 $data = array( 'cache_value' => '' );
372 $format = array( '%s' );
373
374 $where['feed_id'] = $feed_id;
375 $where_format[] = '%s';
376
377 $where['cache_key'] = $type . $this->suffix;
378 $where_format[] = '%s';
379
380 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
381
382 $where['feed_id'] = $feed_id . '_CUSTOMIZER';
383
384 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
385
386 $where['feed_id'] = $feed_id . '_CUSTOMIZER_MODMODE';
387
388 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
389 }
390
391 return $affected;
392 }
393
394 public function get_customizer_cache()
395 {
396 if (strpos($this->feed_id, '_CUSTOMIZER') === false) {
397 $feed_id = $this->feed_id . '_CUSTOMIZER';
398 } else {
399 $feed_id = $this->feed_id;
400 }
401 global $wpdb;
402 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
403
404 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared -- Table name is safely constructed from $wpdb->prefix
405 $sql = $wpdb->prepare(
406 "
407 SELECT * FROM $cache_table_name
408 WHERE feed_id = %s
409 AND cache_key = 'posts'",
410 $feed_id
411 );
412 $results = $wpdb->get_results($sql, ARRAY_A);
413 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
414
415 $return = array();
416 if (! empty($results[0])) {
417 $return = $results[0]['cache_value'];
418 $return = json_decode($return, true);
419
420 $return = isset($return['data']) ? $return['data'] : array();
421 }
422
423 return $return;
424 }
425
426 /**
427 * Get all available caches from the feed cache table.
428 *
429 * @return array
430 *
431 * @since 6.0
432 */
433 private function query_feed_caches()
434 {
435 $feed_cache = wp_cache_get($this->get_wp_cache_key());
436 if (false === $feed_cache) {
437 global $wpdb;
438 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
439
440 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared -- Table name is safely constructed from $wpdb->prefix
441 $sql = $wpdb->prepare(
442 "
443 SELECT * FROM $cache_table_name
444 WHERE feed_id = %s",
445 $this->feed_id
446 );
447
448 $feed_cache = $wpdb->get_results($sql, ARRAY_A);
449 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
450
451 wp_cache_set($this->get_wp_cache_key(), $feed_cache);
452 }
453
454 return $feed_cache;
455 }
456
457 /**
458 * Delete the wp_cache
459 *
460 * @since 6.0
461 */
462 private function clear_wp_cache()
463 {
464 wp_cache_delete($this->get_wp_cache_key());
465 }
466
467 /**
468 * Clear the Customizer posts cache when regular cache is updated.
469 *
470 * This prevents stale empty cache in Customizer when posts are fetched
471 * after the Customizer was already opened (race condition fix).
472 *
473 * @since 2.4.5
474 */
475 private function clear_customizer_posts_cache()
476 {
477 global $wpdb;
478 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
479 $customizer_feed_id = $this->feed_id . '_CUSTOMIZER';
480
481 // Clear standard Customizer cache
482 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed from $wpdb->prefix
483 $wpdb->query(
484 $wpdb->prepare(
485 "UPDATE $cache_table_name
486 SET cache_value = ''
487 WHERE feed_id = %s
488 AND cache_key = 'posts'",
489 $customizer_feed_id
490 )
491 );
492 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
493
494 // Clear MODMODE Customizer caches (with any offset)
495 $mod_mode_pattern = esc_sql((string) $this->feed_id) . '_CUSTOMIZER_MODMODE%';
496 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed from $wpdb->prefix
497 $wpdb->query(
498 $wpdb->prepare(
499 "UPDATE $cache_table_name
500 SET cache_value = ''
501 WHERE feed_id LIKE %s
502 AND cache_key = 'posts'",
503 $mod_mode_pattern
504 )
505 );
506 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
507
508 // Also clear WP object cache for the Customizer feed
509 wp_cache_delete(self::CACHE_KEY . $customizer_feed_id . '_');
510 }
511
512 /**
513 * Key used to get the wp cache key
514 *
515 * @return string
516 *
517 * @since 6.0
518 */
519 private function get_wp_cache_key()
520 {
521 return self::CACHE_KEY . $this->feed_id . '_' . $this->suffix;
522 }
523
524
525
526 /**
527 * Add suffix to cache keys used in the customizer
528 *
529 * @return string
530 *
531 * @since 6.0
532 */
533 private function maybe_customizer_suffix()
534 {
535 $additional_suffix = '';
536 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Used only for cache key generation
537 $in_customizer = ! empty($_POST['previewSettings']) || ( isset($_GET['page']) && $_GET['page'] === 'sbr' );
538
539 if ($in_customizer) {
540 $additional_suffix .= '_CUSTOMIZER';
541
542 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Used only for cache key generation
543 if (! empty($_POST['moderationShoppableMode'])) {
544 $additional_suffix .= '_MODMODE';
545 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Used only for cache key generation
546 $offset = ! empty($_POST['moderationShoppableModeOffset']) ? intval($_POST['moderationShoppableModeOffset']) : '';
547 $additional_suffix .= $offset;
548 }
549 }
550
551 return $additional_suffix;
552 }
553
554 public function update_last_updated()
555 {
556 global $wpdb;
557 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
558
559 $data = array();
560 $format = array();
561 $where_format = array();
562
563 $data['last_updated'] = date('Y-m-d H:i:s');
564 $format[] = '%s';
565
566 $where['feed_id'] = $this->feed_id;
567 $where_format[] = '%s';
568
569 $where['cache_key'] = 'posts';
570 $where_format[] = '%s';
571
572 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
573
574 $data = array();
575 $format = array();
576 $where_format = array();
577
578 $data['last_updated'] = date('Y-m-d H:i:s');
579 $format[] = '%s';
580
581 $where['feed_id'] = $this->feed_id;
582 $where_format[] = '%s';
583
584 $where['cache_key'] = 'header';
585 $where_format[] = '%s';
586
587 $affected = $wpdb->update($cache_table_name, $data, $where, $format, $where_format);
588
589 return $affected;
590 }
591
592 /**
593 * Get active/all cache count.
594 *
595 * @param bool $active when set to true only items updated in the last months are returned.
596 *
597 * @return int
598 */
599 public function get_cache_count($active = false)
600 {
601 global $wpdb;
602 $cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME;
603 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed from $wpdb->prefix
604 $query = "SELECT COUNT(DISTINCT feed_id, cache_key) as cache_count FROM $cache_table_name WHERE feed_id Not Like '%_CUSTOMIZER%'";
605
606 if ($active === true) {
607 $query .= " AND feed_id Not Like '%_MODMODE%' AND last_updated >= DATE_SUB(NOW(), INTERVAL 1 MONTH)";
608 }
609
610 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Query is safely constructed above
611 $sql = $wpdb->prepare($query);
612 $caches = $wpdb->get_results($sql);
613 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
614
615 if (! empty($caches)) {
616 return $caches[0]->cache_count;
617 }
618
619 return 0;
620 }
621
622 }
623