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.11.0 2.10.0 2.9.0 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 / Customizer / DB.php
reviews-feed / class / Common / Customizer Last commit date
Tabs 1 month ago Config.php 6 months ago DB.php 6 days ago ProxyProvider.php 6 months ago ShortcodePreviewProvider.php 6 months ago
DB.php
1020 lines
1 <?php
2
3 // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL, WordPress.DB.PreparedSQLPlaceholders, WordPress.Security.EscapeOutput.OutputNotEscaped
4 // Note: Legacy file with pre-existing PHPCS issues. Direct DB queries and LIKE wildcards required for customizer functionality.
5
6 namespace SmashBalloon\Reviews\Common\Customizer;
7
8 use SmashBalloon\Reviews\Common\Builder\SBR_Sources;
9 use SmashBalloon\Reviews\Common\SBR_Settings;
10 use SmashBalloon\Reviews\Common\Helpers\Data_Encryption;
11
12 class DB extends \Smashballoon\Customizer\V2\DB{
13 /**
14 * Number of feeds per page (override parent for testing: set to 2)
15 * Production value: 20
16 */
17 const RESULTS_PER_PAGE = 20;
18
19 /**
20 * Number of sources per page (for testing: set to 2)
21 * Production value: 40
22 */
23 const SOURCES_PER_PAGE = 20;
24
25 protected $feeds_table = SBR_FEEDS_TABLE;
26 protected $sources_table = SBR_SOURCES_TABLE;
27 protected $caches_table = SBR_FEED_CACHES_TABLE;
28 protected $post_tables = POSTS_TABLE_NAME;
29 protected $custom_source_table = true;
30
31 public function __construct()
32 {
33 global $wpdb;
34 $this->feeds_table = $wpdb->prefix . SBR_FEEDS_TABLE;
35 $this->sources_table = $wpdb->prefix . SBR_SOURCES_TABLE;
36 $this->caches_table = $wpdb->prefix . SBR_FEED_CACHES_TABLE;
37 $this->post_tables = $wpdb->prefix . POSTS_TABLE_NAME;
38 $this->custom_source_table = true;
39 }
40
41 /**
42 * Query the feeds table
43 * Porcess to define the name of the feed when adding new
44 *
45 * @param array $args
46 *
47 * @return array|bool
48 *
49 * @since 1.0
50 */
51 public static function feeds_query_name($feedname)
52 {
53 global $wpdb;
54 $feeds_table_name = $wpdb->prefix . SBR_FEEDS_TABLE;
55 $sql = $wpdb->prepare(
56 "SELECT * FROM $feeds_table_name
57 WHERE feed_name LIKE %s;",
58 $wpdb->esc_like($feedname) . '%'
59 );
60 $count = sizeof($wpdb->get_results($sql, ARRAY_A));
61 return ($count == 0) ? $feedname : $feedname . ' (' . ($count + 1) . ')';
62 }
63
64 /**
65 * Query to Duplicate a Single Feed
66 *
67 * @param array $args
68 *
69 * @return array|bool
70 *
71 * @since 1.0
72 */
73 public static function duplicate_feed_query($feed_id)
74 {
75 global $wpdb;
76 $feeds_table_name = $wpdb->prefix . SBR_FEEDS_TABLE;
77 $wpdb->query(
78 $wpdb->prepare(
79 "INSERT INTO $feeds_table_name (feed_name, settings, author, status)
80 SELECT CONCAT(feed_name, ' (copy)'), settings, author, status
81 FROM $feeds_table_name
82 WHERE id = %d; ",
83 $feed_id
84 )
85 );
86
87 self::copy_feed_style_to_duplicate((int) $feed_id, (int) $wpdb->insert_id);
88
89 echo sbr_json_encode(
90 [
91 'feedsList' => DB::get_feeds_list(),
92 'feedsCount' => DB::feeds_list_count()
93 ]
94 );
95 wp_die();
96 }
97
98 /**
99 * Carry the source feed's rendered CSS onto its duplicate, re-scoped.
100 *
101 * `feed_style` is pre-rendered CSS whose every selector is scoped to the
102 * source feed's container id, so the INSERT above deliberately leaves it out
103 * — copying it verbatim would style the original, not the copy. The column
104 * being absent is what made a duplicate render with base styling only until
105 * someone opened and re-saved it, because the render path reads this column
106 * rather than regenerating from settings (Feed.php:57).
107 *
108 * @param int $source_id Feed the duplicate was copied from.
109 * @param int $new_id Freshly inserted duplicate.
110 *
111 * @return void
112 */
113 private static function copy_feed_style_to_duplicate($source_id, $new_id)
114 {
115 global $wpdb;
116
117 if ($source_id <= 0 || $new_id <= 0) {
118 return;
119 }
120
121 $feeds_table_name = $wpdb->prefix . SBR_FEEDS_TABLE;
122
123 // Already-sanitised DB content going back to the DB: feed_style is passed
124 // through sanitize_text_field() on save (SBR_Feed_Saver.php:280), which is
125 // the only thing standing between it and the unescaped echo at
126 // FeedDisplay.php:87. No new taint enters here, but this path
127 // will faithfully propagate whatever is in the column — so a future write
128 // path that skips that sanitiser would reach the duplicate too.
129 $source_style = $wpdb->get_var(
130 $wpdb->prepare("SELECT feed_style FROM $feeds_table_name WHERE id = %d", $source_id)
131 );
132
133 if (!is_string($source_style) || $source_style === '') {
134 return;
135 }
136
137 // \b so a source id of 19 cannot match the 194 in another feed's selector.
138 $rescoped = preg_replace(
139 '/#' . preg_quote(sbr_container_id($source_id), '/') . '\b/',
140 '#' . sbr_container_id($new_id),
141 $source_style
142 );
143
144 if (!is_string($rescoped) || $rescoped === '') {
145 return;
146 }
147
148 $wpdb->update(
149 $feeds_table_name,
150 ['feed_style' => $rescoped],
151 ['id' => $new_id],
152 ['%s'],
153 ['%d']
154 );
155 }
156
157 /**
158 * Query to Remove Feeds from Database
159 *
160 * @param array $args
161 *
162 * @return array|bool
163 *
164 * @since 1.0
165 */
166 public static function delete_feeds_query($feed_ids_array)
167 {
168 global $wpdb;
169 $feeds_table_name = $wpdb->prefix . SBR_FEEDS_TABLE;
170 $feed_caches_table_name = $wpdb->prefix . SBR_FEED_CACHES_TABLE;
171
172 // Sanitize IDs - ensure they are integers
173 $feed_ids_array = array_map('absint', $feed_ids_array);
174 $feed_ids_array = array_filter($feed_ids_array);
175
176 if (empty($feed_ids_array)) {
177 echo sbr_json_encode([
178 'feedsList' => DB::get_feeds_list(),
179 'feedsCount' => DB::feeds_list_count()
180 ]);
181 wp_die();
182 }
183
184 $feed_ids_string = implode(',', $feed_ids_array);
185
186 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- IDs are sanitized with absint()
187 $wpdb->query("DELETE FROM $feeds_table_name WHERE id IN ($feed_ids_string)");
188 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- IDs are sanitized with absint()
189 $wpdb->query("DELETE FROM $feed_caches_table_name WHERE feed_id IN ($feed_ids_string)");
190
191 echo sbr_json_encode(
192 [
193 'feedsList' => DB::get_feeds_list(),
194 'feedsCount' => DB::feeds_list_count()
195 ]
196 );
197 wp_die();
198 }
199
200 /**
201 * Creates Sources Table
202 *
203 *
204 *
205 * @since 1.0
206 */
207 public function create_sources_table()
208 {
209 if (!function_exists('dbDelta')) {
210 require_once ABSPATH . '/wp-admin/includes/upgrade.php';
211 }
212 global $wpdb;
213 $max_index_length = 191;
214 $charset_collate = '';
215 if (method_exists($wpdb, 'get_charset_collate')) { // get_charset_collate introduced in WP3.5
216 $charset_collate = $wpdb->get_charset_collate();
217 }
218 if ($wpdb->get_var("show tables like '$this->sources_table'") !== $this->sources_table) {
219 $sql = '
220 CREATE TABLE ' . $this->sources_table . " (
221 id bigint(20) unsigned NOT NULL auto_increment,
222 account_id varchar(255) NOT NULL default '',
223 provider varchar(255) NOT NULL default '',
224 access_token varchar(1000) NOT NULL default '',
225 name varchar(255) NOT NULL default '',
226 info text NOT NULL default '',
227 error text NOT NULL default '',
228 expires datetime NOT NULL,
229 last_updated datetime NOT NULL,
230 author bigint(20) unsigned NOT NULL default '1',
231 PRIMARY KEY (id),
232 KEY author (author)
233 ) $charset_collate;";
234 $wpdb->query($sql);
235 }
236 }
237
238
239 /**
240 * Query the sbi_sources table
241 *
242 * @param array $args
243 *
244 * @return array|bool
245 *
246 * @since 1.0
247 */
248 public function source_query($args = array())
249 {
250 global $wpdb;
251
252 $page = 0;
253 if (isset($args['page'])) {
254 $page = (int) $args['page'] - 1;
255 unset($args['page']);
256 }
257
258 $limit = self::SOURCES_PER_PAGE;
259 $offset = max(0, $page * $limit);
260
261 // Handle search parameter
262 $search = '';
263 if (isset($args['search'])) {
264 $search = sanitize_text_field($args['search']);
265 unset($args['search']);
266 }
267
268 if (empty($args)) {
269 // Build WHERE clause for search
270 $where_clause = '';
271 if (! empty($search)) {
272 $search_term = '%' . $wpdb->esc_like($search) . '%';
273 $where_clause = $wpdb->prepare(
274 " WHERE s.name LIKE %s OR s.provider LIKE %s ",
275 $search_term,
276 $search_term
277 );
278 }
279
280 $sql = "SELECT s.id, s.account_id, s.provider, s.access_token, s.name, s.info, s.error, s.expires, count(f.id) as used_in,
281 (SELECT count(p.id) FROM $this->post_tables p WHERE s.account_id = p.provider_id) as reviews_number
282 FROM $this->sources_table s
283 LEFT JOIN $this->feeds_table f ON f.settings LIKE CONCAT('%', s.account_id, '%')
284 {$where_clause}
285 GROUP BY s.account_id
286 LIMIT $limit
287 OFFSET $offset;
288 ";
289
290 $results = $wpdb->get_results($sql, ARRAY_A);
291
292 if (empty($results)) {
293 return array();
294 }
295
296 $i = 0;
297 foreach ($results as $result) {
298 if ((int) $result['used_in'] > 0) {
299 $results[ $i ]['instances'] = $wpdb->get_results($wpdb->prepare(
300 "SELECT *
301 FROM $this->feeds_table
302 WHERE settings LIKE CONCAT('%', %s, '%')
303 GROUP BY id
304 LIMIT 100;
305 ",
306 $result['account_id']
307 ), ARRAY_A);
308 }
309 $i++;
310 }
311
312 return $results;
313 }
314
315
316 if (! empty($args['name'])) {
317 return $wpdb->get_results(
318 $wpdb->prepare(
319 "
320 SELECT * FROM $this->sources_table
321 WHERE name = %s;
322 ",
323 $args['name']
324 ),
325 ARRAY_A
326 );
327 }
328
329
330 if (! isset($args['id'])) {
331 return false;
332 }
333
334 if (is_array($args['id'])) {
335 $id_array = array();
336 foreach ($args['id'] as $id) {
337 $id_array[] = esc_sql($id);
338 }
339 } elseif (strpos($args['id'], ',') !== false) {
340 $id_array = explode(',', str_replace(' ', '', esc_sql($args['id'])));
341 }
342
343 if (isset($id_array)) {
344 $id_array = array_filter($id_array, function ($value) {
345 return !is_null($value) && $value !== '' && !empty($value) && !is_array($value);
346 });
347 $id_string = "'" . implode("' , '", array_map('esc_sql', $id_array)) . "'";
348 }
349
350 $privilege = '';
351 if (isset($id_string)) {
352 $sql = "
353 SELECT * FROM $this->sources_table
354 WHERE account_id IN ($id_string);
355 ";
356 } else {
357 $sql = $wpdb->prepare(
358 "
359 SELECT * FROM $this->sources_table
360 WHERE account_id = %s;
361 ",
362 $args['id']
363 );
364 }
365 return $wpdb->get_results($sql, ARRAY_A);
366 }
367
368
369 /**
370 * Query the sbi_sources table to get number of sources
371 *
372 * @param array $args Optional args with 'search' parameter
373 *
374 * @return int
375 *
376 * @since 1.0
377 */
378 public function source_query_count($args = array())
379 {
380 global $wpdb;
381
382 $where_clause = '';
383 if (! empty($args['search'])) {
384 $search_term = '%' . $wpdb->esc_like(sanitize_text_field($args['search'])) . '%';
385 $where_clause = $wpdb->prepare(
386 " WHERE name LIKE %s OR provider LIKE %s ",
387 $search_term,
388 $search_term
389 );
390 }
391
392 $source_count = $wpdb->get_var("SELECT count(*) FROM $this->sources_table {$where_clause}");
393 return (int) $source_count;
394 }
395
396 /**
397 * Query the feeds table with search support
398 *
399 * @param array $args
400 *
401 * @return array|bool
402 *
403 * @since 2.3.0
404 */
405 public function feeds_query($args = array())
406 {
407 global $wpdb;
408
409 $page = 0;
410 if (isset($args['page'])) {
411 $page = (int) $args['page'] - 1;
412 unset($args['page']);
413 }
414
415 $offset = max(0, $page * self::RESULTS_PER_PAGE);
416
417 // Handle search parameter
418 $search = '';
419 if (isset($args['search'])) {
420 $search = sanitize_text_field($args['search']);
421 unset($args['search']);
422 }
423
424 if (isset($args['id'])) {
425 $sql = $wpdb->prepare(
426 "SELECT * FROM $this->feeds_table WHERE id = %d;",
427 $args['id']
428 );
429 } else {
430 // Build WHERE clause for search
431 $where_clause = '';
432 if (! empty($search)) {
433 $search_term = '%' . $wpdb->esc_like($search) . '%';
434 $where_clause = $wpdb->prepare(
435 " WHERE feed_name LIKE %s ",
436 $search_term
437 );
438 }
439
440 $sql = $wpdb->prepare(
441 "SELECT * FROM $this->feeds_table {$where_clause} ORDER BY last_modified DESC LIMIT %d OFFSET %d;",
442 self::RESULTS_PER_PAGE,
443 $offset
444 );
445 }
446
447 return $wpdb->get_results($sql, ARRAY_A);
448 }
449 /**
450 * New source (connected account) data is added to the
451 * sbr_sources table and the new insert ID is returned
452 *
453 * @param array $to_insert
454 *
455 * @return false|int
456 *
457 * @since 1.0
458 */
459 public function source_insert($to_insert)
460 {
461 global $wpdb;
462 $data = array();
463 $format = array();
464 $where = array();
465 $where_format = array();
466 if (isset($to_insert['account_id'])) {
467 $data['account_id'] = $to_insert['account_id'];
468 $format[] = '%s';
469 }
470 if (isset($to_insert['provider'])) {
471 $data['provider'] = $to_insert['provider'];
472 $format[] = '%s';
473 }
474 if (isset($to_insert['name'])) {
475 $data['name'] = $to_insert['name'];
476 $format[] = '%s';
477 }
478 if (isset($to_insert['info'])) {
479 $data['info'] = $to_insert['info'];
480 $format[] = '%s';
481 }
482 if (isset($to_insert['access_token'])) {
483 $data['access_token'] = $to_insert['access_token'];
484 $format[] = '%s';
485 }
486 if (isset($to_insert['error'])) {
487 $data['error'] = $to_insert['error'];
488 $format[] = '%s';
489 }
490 if (isset($to_insert['expires'])) {
491 $data['expires'] = $to_insert['expires'];
492 $format[] = '%s';
493 } else {
494 $data['expires'] = '2100-12-30 00:00:00';
495 $format[] = '%s';
496 }
497 $data['last_updated'] = gmdate('Y-m-d H:i:s');
498 $format[] = '%s';
499 if (isset($to_insert['author'])) {
500 $data['author'] = $to_insert['author'];
501 $format[] = '%d';
502 } else {
503 $data['author'] = get_current_user_id();
504 $format[] = '%d';
505 }
506 $affected = $wpdb->insert($this->sources_table, $data, $format);
507
508 return $affected;
509 }
510
511 /**
512 * Update a source (connected account)
513 *
514 * @param array $to_update
515 * @param array $where_data
516 *
517 * @return false|int
518 *
519 * @since 1.0
520 */
521 public function source_update($to_update, $where_data)
522 {
523 global $wpdb;
524
525 $data = array();
526 $where = array();
527 $format = array();
528 $where_format = array();
529
530 if (isset($to_update['name'])) {
531 $data['name'] = $to_update['name'];
532 $format[] = '%s';
533 }
534 if (isset($to_update['info'])) {
535 $data['info'] = $to_update['info'];
536 $format[] = '%s';
537 }
538 if (isset($to_update['last_updated'])) {
539 $data['last_updated'] = $to_update['last_updated'];
540 $format[] = '%s';
541 }
542 if (isset($to_update['access_token'])) {
543 $data['access_token'] = $to_update['access_token'];
544 $format[] = '%s';
545 }
546 if (isset($where_data['id'])) {
547 $where['account_id'] = $where_data['id'];
548 $where_format[] = '%s';
549 }
550 if (isset($where_data['provider'])) {
551 $where['provider'] = $where_data['provider'];
552 $where_format[] = '%s';
553 }
554
555
556 $affected = $wpdb->update($this->sources_table, $data, $where, $format, $where_format);
557 return $affected;
558 }
559
560 /**
561 * Update a source (connected account)
562 *
563 * @param array $to_update
564 * @param array $where_data
565 *
566 * @return false|int
567 *
568 * @since 1.0
569 */
570 public function get_single_source($args)
571 {
572 global $wpdb;
573 $query = "SELECT s.*, count(f.id) as used_in FROM $this->sources_table as s LEFT JOIN $this->feeds_table f ON f.settings LIKE CONCAT('%', s.account_id, '%') WHERE s.account_id = %s AND s.provider = %s";
574 $sql = $wpdb->prepare(
575 $query,
576 $args['id'],
577 $args['provider']
578 );
579 $affected = $wpdb->get_results($sql, ARRAY_A);
580 $i = 0;
581 foreach ($affected as $result) {
582 if ((int) $result['used_in'] > 0) {
583 $affected[ $i ]['instances'] = $wpdb->get_results($wpdb->prepare(
584 "SELECT id
585 FROM $this->feeds_table
586 WHERE settings LIKE CONCAT('%', %s, '%')
587 GROUP BY id
588 LIMIT 100;
589 ",
590 $result['account_id']
591 ), ARRAY_A);
592 }
593 $i++;
594 }
595 return !isset($affected[0]['account_id']) || is_null($affected[0]['account_id']) ? [] : $affected;
596 }
597
598
599 /**
600 * Get feeds list with optional location data.
601 *
602 * @param array $feeds_args Query arguments.
603 * @param bool $skip_locations Skip expensive shortcode location queries. Default false.
604 *
605 * @return array
606 */
607 public static function get_feeds_list($feeds_args = array(), $skip_locations = false)
608 {
609
610 if (! empty($_GET['feed_id'])) {
611 return array();
612 }
613 $db = new DB();
614 $feeds_data = $db->feeds_query($feeds_args);
615
616 $i = 0;
617 foreach ($feeds_data as $single_feed) {
618 if ($skip_locations) {
619 $feeds_data[ $i ]['instance_count'] = 0;
620 $feeds_data[ $i ]['location_summary'] = array();
621 } else {
622 // Use direct content scanning to find shortcode usage
623 // This finds shortcodes in drafts, unpublished content, and all post types
624 $locations = self::get_feed_shortcode_locations($single_feed['id'], DB::RESULTS_PER_PAGE);
625 $count = self::count_feed_shortcode_locations($single_feed['id']);
626 $feeds_data[ $i ]['instance_count'] = $count;
627 $feeds_data[ $i ]['location_summary'] = $locations;
628 }
629
630 $settings = json_decode($feeds_data[ $i ]['settings'], true);
631
632 $settings['feed'] = $single_feed['id'];
633
634 $reviews_feed_settings = new SBR_Settings($settings, sbr_settings_defaults());
635
636 $feeds_data[ $i ]['settings'] = $reviews_feed_settings->get_settings();
637 $feeds_data[ $i ]['sourcesList'] = SBR_Sources::get_sources_list([
638 'id' => $feeds_data[ $i ]['settings']['sources']
639 ]);
640 $i++;
641 }
642 return $feeds_data;
643 }
644
645 /**
646 * Query to Remove Source from Database
647 *
648 * @param array $source_id
649 *
650 * @since 6.0
651 */
652 public function delete_source($source_id)
653 {
654 global $wpdb;
655 return $wpdb->query(
656 $wpdb->prepare(
657 "DELETE FROM $this->sources_table WHERE id = %d; ",
658 $source_id
659 )
660 );
661 }
662
663 /**
664 * Count the sbr_feeds table
665 *
666 * @param array $args Optional args with 'search' parameter
667 *
668 * @return int
669 *
670 * @since 4.0
671 */
672 public static function feeds_list_count($args = array())
673 {
674 global $wpdb;
675 $feeds_table_name = $wpdb->prefix . SBR_FEEDS_TABLE;
676
677 $where_clause = '';
678 if (! empty($args['search'])) {
679 $search_term = '%' . $wpdb->esc_like(sanitize_text_field($args['search'])) . '%';
680 $where_clause = $wpdb->prepare(
681 " WHERE feed_name LIKE %s ",
682 $search_term
683 );
684 }
685
686 $results = $wpdb->get_results(
687 "SELECT COUNT(*) AS num_entries FROM $feeds_table_name {$where_clause}",
688 ARRAY_A
689 );
690 return isset($results[0]['num_entries']) ? (int)$results[0]['num_entries'] : 0;
691 }
692
693 /**
694 * Get Facebook Sources List
695 *
696 * @return array
697 *
698 * @since X.X
699 */
700 public static function get_facebook_sources()
701 {
702 global $wpdb;
703 $source_table = $wpdb->prefix . SBR_SOURCES_TABLE;
704 $query = "SELECT * FROM $source_table as s WHERE s.provider = %s";
705 $sql = $wpdb->prepare(
706 $query,
707 'facebook'
708 );
709 $results = $wpdb->get_results($sql, ARRAY_A);
710 return $results;
711 }
712
713 /**
714 * Query to Remove Source from Database
715 *
716 * @param int $source_id
717 *
718 * @return array|bool
719 *
720 * @since X.X
721 */
722 public static function delete_source_by_id($source_id)
723 {
724 global $wpdb;
725 $sources_table_name = $wpdb->prefix . SBR_SOURCES_TABLE;
726 $wpdb->query(
727 $wpdb->prepare(
728 "DELETE FROM $sources_table_name WHERE id = %d; ",
729 $source_id
730 )
731 );
732 }
733
734 /**
735 * Remove ALL Facebook Posts
736 *
737 * @return void
738 *
739 * @since X.X
740 */
741 public static function clear_facebook_feed_posts()
742 {
743 global $wpdb;
744 $posts_table_name = $wpdb->prefix . SBR_POSTS_TABLE;
745
746 if ($wpdb->get_var("show tables like '$posts_table_name'") === $posts_table_name) {
747 $wpdb->query("DELETE FROM $posts_table_name WHERE provider = 'facebook'");
748 }
749 }
750
751 /**
752 * Remove ALL Facebook Sources
753 *
754 * @return void
755 *
756 * @since X.X
757 */
758 public static function clear_facebook_sources()
759 {
760 global $wpdb;
761 $sources_table_name = $wpdb->prefix . SBR_SOURCES_TABLE;
762
763 if ($wpdb->get_var("show tables like '$sources_table_name'") === $sources_table_name) {
764 $wpdb->query("DELETE FROM $sources_table_name WHERE provider = 'facebook'");
765 }
766 }
767
768
769
770 /**
771 * Get Facebook Cached Posts List
772 *
773 * @return array
774 *
775 * @since X.X
776 */
777 public static function get_facebook_cached_posts()
778 {
779 global $wpdb;
780 $posts_table = $wpdb->prefix . SBR_POSTS_TABLE;
781 $query = "SELECT * FROM $posts_table as s WHERE s.provider = %s";
782 $sql = $wpdb->prepare(
783 $query,
784 'facebook'
785 );
786 $results = $wpdb->get_results($sql, ARRAY_A);
787 return $results;
788 }
789
790
791 /**
792 * Update a single Post
793 *
794 * @param array $to_update
795 * @param array $where_data
796 *
797 * @return void
798 *
799 * @since 1.0
800 */
801 public static function single_post_update($to_update, $where_data)
802 {
803 global $wpdb;
804 $data = [];
805 $where = [];
806 $format = [];
807 $where_format = [];
808 if (isset($to_update['json_data'])) {
809 $data['json_data'] = $to_update['json_data'];
810 $format[] = '%s';
811 }
812 if (isset($to_update['post_content'])) {
813 $data['post_content'] = $to_update['post_content'];
814 $format[] = '%s';
815 }
816 if (isset($where_data['id'])) {
817 $where['id'] = $where_data['id'];
818 $where_format[] = '%s';
819 }
820 if (isset($where_data['provider'])) {
821 $where['provider'] = $where_data['provider'];
822 $where_format[] = '%s';
823 }
824 $feeds_posts_table_name = $wpdb->prefix . 'sbr_reviews_posts';
825 $wpdb->update($feeds_posts_table_name, $data, $where, $format, $where_format);
826 }
827
828 /**
829 * Get Collections List
830 *
831 * @return array
832 *
833 * @since X.X
834 */
835 public static function get_collections_list()
836 {
837 global $wpdb;
838 $sources_table = $wpdb->prefix . SBR_SOURCES_TABLE;
839 $query = "SELECT * FROM $sources_table as s WHERE s.provider = %s";
840 $sql = $wpdb->prepare(
841 $query,
842 'collection'
843 );
844 $results = $wpdb->get_results($sql, ARRAY_A);
845 return $results;
846 }
847
848 /**
849 * Find all posts/pages containing a specific feed shortcode.
850 *
851 * Scans post_content directly in the database to find shortcode usage,
852 * including drafts and unpublished content that may not have been rendered yet.
853 *
854 * @param int|string $feed_id The feed ID to search for.
855 * @param int $limit Maximum number of results to return.
856 *
857 * @return array Array of locations where the feed shortcode is used.
858 *
859 * @since 2.3.0
860 */
861 public static function get_feed_shortcode_locations($feed_id, $limit = 20)
862 {
863 global $wpdb;
864
865 if (empty($feed_id)) {
866 return array();
867 }
868
869 $feed_id = (int) $feed_id;
870
871 // The query below is a leading-wildcard LIKE over the post_content LONGTEXT
872 // column, which is unindexable and forces a full-table scan + filesort.
873 // Running it on every editor/admin page load hangs large sites, so cache the
874 // result for a short window. See SMASH-1591.
875 $cache_key = 'sbr_feed_loc_' . $feed_id . '_' . (int) $limit;
876 $cached = get_transient($cache_key);
877 if (false !== $cached) {
878 return $cached;
879 }
880
881 // Search for shortcode patterns in post_content
882 // Matches: [reviews-feed feed=123] or [reviews-feed feed="123"] or [reviews-feed feed='123']
883 $results = $wpdb->get_results(
884 $wpdb->prepare(
885 "SELECT ID, post_title, post_type, post_status
886 FROM {$wpdb->posts}
887 WHERE post_content LIKE %s
888 AND post_type NOT IN ('revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation')
889 AND post_status NOT IN ('auto-draft', 'inherit', 'trash')
890 ORDER BY post_date DESC
891 LIMIT %d",
892 '%[reviews-feed%feed=' . $wpdb->esc_like((string) $feed_id) . '%',
893 $limit
894 ),
895 ARRAY_A
896 );
897
898 $locations = array();
899 foreach ((array) $results as $row) {
900 $page_text = ! empty($row['post_title']) ? $row['post_title'] : __('(no title)', 'reviews-feed');
901
902 // Add status indicator for non-published posts
903 if ($row['post_status'] !== 'publish') {
904 $status_labels = array(
905 'draft' => __('Draft', 'reviews-feed'),
906 'pending' => __('Pending', 'reviews-feed'),
907 'private' => __('Private', 'reviews-feed'),
908 'future' => __('Scheduled', 'reviews-feed'),
909 );
910 $status_label = isset($status_labels[ $row['post_status'] ])
911 ? $status_labels[ $row['post_status'] ]
912 : ucfirst($row['post_status']);
913 $page_text .= '' . $status_label;
914 }
915
916 $locations[] = array(
917 'link' => esc_url(get_the_permalink($row['ID'])),
918 'page_text' => $page_text,
919 'post_id' => $row['ID'],
920 'post_type' => $row['post_type'],
921 );
922 }
923
924 set_transient($cache_key, $locations, 5 * MINUTE_IN_SECONDS);
925
926 return $locations;
927 }
928
929 /**
930 * Count posts/pages containing a specific feed shortcode.
931 *
932 * @param int|string $feed_id The feed ID to search for.
933 *
934 * @return int Number of posts containing the shortcode.
935 *
936 * @since 2.3.0
937 */
938 public static function count_feed_shortcode_locations($feed_id)
939 {
940 global $wpdb;
941
942 if (empty($feed_id)) {
943 return 0;
944 }
945
946 $feed_id = (int) $feed_id;
947
948 // Same unindexable full-table LONGTEXT scan as get_feed_shortcode_locations();
949 // cache for a short window so it does not run on every page load. See SMASH-1591.
950 $cache_key = 'sbr_feed_loc_count_' . $feed_id;
951 $cached = get_transient($cache_key);
952 if (false !== $cached) {
953 return (int) $cached;
954 }
955
956 $count = $wpdb->get_var(
957 $wpdb->prepare(
958 "SELECT COUNT(*)
959 FROM {$wpdb->posts}
960 WHERE post_content LIKE %s
961 AND post_type NOT IN ('revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation')
962 AND post_status NOT IN ('auto-draft', 'inherit', 'trash')",
963 '%[reviews-feed%feed=' . $wpdb->esc_like((string) $feed_id) . '%'
964 )
965 );
966
967 set_transient($cache_key, (int) $count, 5 * MINUTE_IN_SECONDS);
968
969 return (int) $count;
970 }
971
972 /**
973 * Get All Collection Reviews
974 *
975 * @return array
976 *
977 * @since X.X
978 */
979
980 public static function get_collections_reviews($account_id)
981 {
982 global $wpdb;
983 $post_table = $wpdb->prefix . SBR_POSTS_TABLE;
984 $source_table = $wpdb->prefix . SBR_SOURCES_TABLE;
985
986 $collection = $wpdb->get_row(
987 $wpdb->prepare(
988 "SELECT * FROM $source_table WHERE account_id = %s",
989 $account_id
990 ),
991 ARRAY_A
992 );
993
994 if (!isset($collection['account_id'])) {
995 return [];
996 }
997
998 $posts_list = $wpdb->get_results(
999 $wpdb->prepare(
1000 "SELECT * FROM $post_table WHERE provider_id = %s",
1001 $collection['account_id']
1002 ),
1003 ARRAY_A
1004 );
1005
1006 $posts_list_result = [];
1007 //Loop over all posts to decrypt Facebook Posts
1008 $encryption = new Data_Encryption();
1009
1010 foreach ($posts_list as $post) {
1011 $post['post_content'] = $post['provider'] === 'facebook' ? $encryption->maybe_decrypt($post['post_content']) : $post['post_content'];
1012 $post['json_data'] = $post['provider'] === 'facebook' ? $encryption->maybe_decrypt($post['json_data']) : $post['json_data'];
1013 array_push($posts_list_result, $post);
1014 }
1015
1016 $collection['reviewsList'] = $posts_list_result;
1017 return $collection;
1018 }
1019 }
1020