PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.6.7
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.6.7
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 / Util.php
reviews-feed / class / Common Last commit date
Admin 2 months ago Builder 2 months ago Customizer 2 months ago Exceptions 2 months ago Helpers 2 months ago Integrations 2 months ago Migrations 2 months ago ReviewAlerts 2 months ago Services 2 months ago Settings 2 months ago Support 2 months ago Traits 2 months ago Utils 2 months ago AuthorizationStatusCheck.php 2 months ago BusinessDataCache.php 2 months ago Clear_Cache.php 2 months ago Container.php 2 months ago DisplayElements.php 2 months ago Email_Notification.php 2 months ago Error_Reporter.php 2 months ago Feed.php 2 months ago FeedCache.php 2 months ago FeedCacheUpdater.php 2 months ago FeedDisplay.php 2 months ago Feed_Locator.php 2 months ago Parser.php 2 months ago PostAggregator.php 2 months ago RemoteRequest.php 2 months ago SBR_Education.php 2 months ago SBR_Settings.php 2 months ago ServiceContainer.php 2 months ago SinglePostCache.php 2 months ago TemplateRenderer.php 2 months ago Tooltip_Wizard.php 2 months ago Util.php 2 months ago
Util.php
1672 lines
1 <?php
2
3 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
4 // Note: Table name interpolation is safe (uses internal constants, not user input).
5
6 namespace SmashBalloon\Reviews\Common;
7
8 use SmashBalloon\Reviews\Common\Builder\SBR_Sources;
9 use SmashBalloon\Reviews\Common\Customizer\DB;
10 use SmashBalloon\Reviews\Common\Helpers\SBR_Error_Handler;
11 use SmashBalloon\Reviews\Pro\Integrations\Providers\EDD;
12
13 class Util
14 {
15 public static function isProduction()
16 {
17 return SBR_PRODUCTION;
18 }
19
20 public static function UTMCampaign()
21 {
22 return '';
23 }
24
25 /**
26 * Get feature flags from config file
27 *
28 * @return array
29 */
30 public static function get_feature_flags()
31 {
32 $config_file = SBR_PLUGIN_DIR . 'config/feature-flags.php';
33 if (file_exists($config_file)) {
34 return include $config_file;
35 }
36 return [];
37 }
38
39 /**
40 * Check if a provider is disabled via feature flags
41 *
42 * @param string $provider_type Provider type to check
43 * @return bool
44 */
45 public static function is_provider_disabled($provider_type)
46 {
47 $flags = self::get_feature_flags();
48 $disabled = isset($flags['disabled_providers']) ? $flags['disabled_providers'] : [];
49 return in_array($provider_type, $disabled, true);
50 }
51
52 /**
53 * Resolve EDD's pluginRequired / pluginRequiredMessage for the customizer
54 * provider tile. Strict: in Pro the gate requires both EDD core AND the
55 * EDD Reviews extension — otherwise the modal would let users add a source
56 * the integration can't actually capture new reviews for.
57 *
58 * Free builds may not ship class/Pro/Integrations/Providers/EDD.php, so
59 * fall back to the prior core-only check there. Free never reaches this
60 * tile in the disabled-from-extension state anyway (EDD shows as an
61 * upsell, not a regular provider), but the fallback keeps parity with
62 * pre-existing behavior.
63 *
64 * @return array Two-key map: pluginRequired (bool), pluginRequiredMessage (string)
65 */
66 private static function get_edd_provider_status()
67 {
68 if (class_exists(EDD::class)) {
69 return [
70 'pluginRequired' => ! EDD::is_active_static(),
71 'pluginRequiredMessage' => EDD::plugin_required_message(),
72 ];
73 }
74
75 return [
76 'pluginRequired' => ! ( class_exists('Easy_Digital_Downloads') || defined('EDD_VERSION') ),
77 'pluginRequiredMessage' => __('Enable Easy Digital Downloads plugin to use it as a source', 'reviews-feed'),
78 ];
79 }
80
81 public static function get_providers()
82 {
83 $campaign = self::sbr_is_pro() ? 'reviews-pro' : 'reviews-free';
84
85 $providers = [
86 [
87 'type' => 'google',
88 'name' => 'Google',
89 'heading' => __('Place ID', 'reviews-feed'),
90 'placeholder' => __('Enter Place ID', 'reviews-feed'),
91 'docLink' => 'https://smashballoon.com/doc/creating-a-google-api-key/?utm_campaign=' . $campaign . '&utm_source=settings&utm_medium=docs'
92 ],
93 [
94 'type' => 'facebook',
95 'name' => 'Facebook'
96 ],
97 [
98 'type' => 'tripadvisor',
99 'name' => 'TripAdvisor',
100 'heading' => __('Page URL', 'reviews-feed'),
101 'placeholder' => __('https://tripadvisor.com/...', 'reviews-feed'),
102 'apiKey' => true,
103 'mandatoryApiKey' => true,
104 'docLink' => 'https://smashballoon.com/doc/creating-a-tripadvisor-api-key/?utm_campaign=' . $campaign . '&utm_source=settings&utm_medium=docs'
105 ],
106 [
107 'type' => 'yelp',
108 'name' => 'Yelp',
109 'heading' => __('Page URL', 'reviews-feed'),
110 'placeholder' => __('https://yelp.com/...', 'reviews-feed'),
111 'docLink' => 'https://smashballoon.com/doc/creating-a-yelp-api-key/?utm_campaign=' . $campaign . '&utm_source=settings&utm_medium=docs'
112 ],
113 [
114 'type' => 'trustpilot',
115 'name' => 'TrustPilot',
116 'heading' => __('Page URL', 'reviews-feed'),
117 'placeholder' => __('https://trustpilot.com/review/...', 'reviews-feed'),
118 'onlyDetails' => true
119 ],
120 [
121 'type' => 'wordpress.org',
122 'name' => 'WordPress.org',
123 'heading' => __('Page URL', 'reviews-feed'),
124 'placeholder' => __('https://wordpress.org/...', 'reviews-feed'),
125 'onlyDetails' => true
126 ],
127 [
128 'type' => 'woocommerce',
129 'name' => 'WooCommerce',
130 'heading' => __('Product', 'reviews-feed'),
131 'placeholder' => __('Select a product', 'reviews-feed'),
132 'onlyDetails' => true,
133 'isLocal' => true,
134 'pluginRequired' => ! ( class_exists('WooCommerce') || function_exists('WC') ),
135 'pluginRequiredMessage' => __('Enable WooCommerce plugin to use it as a source', 'reviews-feed'),
136 ],
137 array_merge(
138 [
139 'type' => 'edd',
140 'name' => 'Easy Digital Downloads',
141 'heading' => __('Download', 'reviews-feed'),
142 'placeholder' => __('Select a download', 'reviews-feed'),
143 'onlyDetails' => true,
144 'isLocal' => true,
145 ],
146 self::get_edd_provider_status()
147 ),
148 [
149 'type' => 'airbnb',
150 'name' => 'Airbnb',
151 'heading' => __('Listing URL', 'reviews-feed'),
152 'placeholder' => __('https://www.airbnb.com/rooms/...', 'reviews-feed'),
153 'onlyDetails' => true
154 ],
155 [
156 'type' => 'booking',
157 'name' => 'Booking.com',
158 'heading' => __('Hotel URL', 'reviews-feed'),
159 'placeholder' => __('https://www.booking.com/hotel/...', 'reviews-feed'),
160 'onlyDetails' => true
161 ],
162 [
163 'type' => 'aliexpress',
164 'name' => 'AliExpress',
165 'heading' => __('Product URL', 'reviews-feed'),
166 'placeholder' => __('https://www.aliexpress.com/item/...', 'reviews-feed'),
167 'onlyDetails' => true
168 ]
169 ];
170
171 // Filter out disabled providers
172 $providers = array_filter($providers, function ($provider) {
173 return ! self::is_provider_disabled($provider['type']);
174 });
175
176 return array_values($providers);
177 }
178
179 /**
180 * Used as a listener for the account connection process. If
181 * data is returned from the account connection processed it's used
182 * to generate the list of possible sources to chose from.
183 *
184 * @return array|bool
185 *
186 * @since 1.0
187 */
188 public static function maybe_source_connection_data()
189 {
190 $nonce = !empty($_GET['cff_con']) ? sanitize_key($_GET['cff_con']) : '';
191 if (!wp_verify_nonce($nonce, 'cff_con')) {
192 return false;
193 }
194
195 if (
196 isset($_GET['cff_access_token'])
197 && isset($_GET['cff_final_response'])
198 && $_GET['cff_final_response'] == 'true'
199 ) {
200 $access_token = sanitize_text_field(wp_unslash($_GET['cff_access_token']));
201 $return = Util::retrieve_available_pages($access_token);
202
203 if ($return) {
204 return $return;
205 } else {
206 return array('error' => __('Unable to connect to Facebook to retrieve account information.', 'reviews-feed'));
207 }
208 }
209 return false;
210 }
211
212 /**
213 * Uses the Facebook API to retrieve a list of pages for the
214 * access token
215 *
216 * @param string $access_token
217 *
218 * @return array|bool
219 *
220 * @since 1.0
221 */
222 public static function retrieve_available_pages($access_token)
223 {
224 //Get User Info
225 $user_url = 'https://graph.facebook.com/me?fields=name,id,picture&access_token=' . urlencode($access_token);
226 $user_id_data = wp_remote_retrieve_body(wp_remote_get($user_url));
227 $user_id_data_arr = json_decode($user_id_data, true);
228
229 $url = 'https://graph.facebook.com/me/accounts?fields=access_token,name,id,overall_star_rating,rating_count&limit=500&access_token=' . urlencode($access_token);
230 $pages_data = wp_remote_retrieve_body(wp_remote_get($url));
231 $pages_data_arr = json_decode($pages_data, true);
232
233
234 if (isset($pages_data_arr['data'])) {
235 $return = [
236 'user' => $user_id_data_arr,
237 'pages' => $pages_data_arr['data'],
238 ];
239
240 return $return;
241 } elseif (isset($pages_data_arr['error'])) {
242 return $pages_data_arr;
243 } else {
244 return [
245 'error' => [
246 'code' => 'HTTP Request',
247 'message' => __('Your server could not complete a remote request to Facebook\'s API. Your host may be blocking access or
248 there may be a problem with your server.', 'reviews-feed')
249 ]
250 ];
251 }
252 }
253
254 public static function currentPageIs($page)
255 {
256 $current_screen = get_current_screen();
257 return $current_screen !== null && !empty($current_screen) && strpos($current_screen->id, $page) !== false;
258 }
259
260 /**
261 * Check Notices for no Provider API KEY
262 *
263 *
264 * @return boolean
265 *
266 * @since 1.0
267 */
268 public static function check_notice_provider_nokey($api_limits)
269 {
270 $noapi_providers = ['wordpress.org', 'trustpilot'];
271 $is_true = $api_limits == $noapi_providers ? false : true;
272
273 foreach ($noapi_providers as $provider) {
274 if ($api_limits == [$provider]) {
275 $is_true = false;
276 }
277 }
278 return $is_true;
279 }
280
281 /**
282 * Returns a list of notices to be displayed in the SB Reviews Pages
283 *
284 *
285 * @return array
286 *
287 * @since 1.0
288 */
289 public static function get_plugin_notices()
290 {
291 $notices = [];
292
293 $api_limits = get_option('sbr_apikeys_limit', []);
294 $should_show_notice = is_array($api_limits) && sizeof($api_limits) > 0 && Util::check_notice_provider_nokey($api_limits);
295 if ($should_show_notice) {
296 array_push($notices, [
297 'type' => 'error',
298 'heading' => __('New reviews will not display until an API key is entered for your sources.', 'reviews-feed'),
299 'description' => __('Due to API limitations your feeds will not show new reviews until you enter an API key on the settings page.', 'reviews-feed'),
300 'actions' => [
301 [
302 'type' => 'primary',
303 'text' => __('How to create an API key', 'reviews-feed'),
304 'link' => admin_url('admin.php?page=sbr-settings')
305 ],
306 [
307 'type' => 'secondary',
308 'text' => __('Go to Settings page', 'reviews-feed'),
309 'link' => admin_url('admin.php?page=sbr-settings')
310 ]
311 ]
312 ]);
313 }
314
315 $noAPIKeyProviders = ['trustpilot', 'wordpress.org'];
316 $noAPIProviderxists = count(array_intersect($noAPIKeyProviders, $api_limits)) ? true : false;
317
318 if ($noAPIProviderxists) {
319 array_push($notices, [
320 'type' => 'error',
321 'heading' => __('You have reached the number of allowed sources.', 'reviews-feed'),
322 'description' => __('Due to API limitations your feeds will not show new reviews because you have reached the number of allowed sources.', 'reviews-feed'),
323 ]);
324 }
325
326 return $notices;
327 }
328
329 /**
330 * Get Smahballoon Plugins Info
331 *
332 * @since 1.0
333 */
334 public static function get_plugins_info()
335 {
336 $installed_plugins = get_plugins();
337 $campaign = self::sbr_is_pro() ? 'reviews-pro' : 'reviews-free';
338
339 $plugins_list = [
340 'facebook' => [
341 'free' => 'custom-facebook-feed/custom-facebook-feed.php',
342 'pro' => 'custom-facebook-feed-pro/custom-facebook-feed.php',
343 'link' => 'https://smashballoon.com/custom-facebook-feed/?utm_campaign=' . $campaign . '&utm_source=about-us&utm_medium=marketing'
344 ],
345 'instagram' => [
346 'free' => 'instagram-feed/instagram-feed.php',
347 'pro' => 'instagram-feed-pro/instagram-feed.php',
348 'link' => 'https://smashballoon.com/instagram-feed/?utm_campaign=' . $campaign . '&utm_source=about-us&utm_medium=marketing'
349 ],
350 'twitter' => [
351 'free' => 'custom-twitter-feeds/custom-twitter-feed.php',
352 'pro' => 'custom-twitter-feeds-pro/custom-twitter-feed.php',
353 'link' => 'https://smashballoon.com/custom-twitter-feeds/?utm_campaign=' . $campaign . '&utm_source=about-us&utm_medium=marketing'
354 ],
355 'youtube' => [
356 'free' => 'feeds-for-youtube/youtube-feed.php',
357 'pro' => 'youtube-feed-pro/youtube-feed.php',
358 'link' => 'https://smashballoon.com/youtube-feed/?utm_campaign=' . $campaign . '&utm_source=about-us&utm_medium=marketing'
359 ]
360 ];
361
362 foreach ($plugins_list as $name => $plugin) {
363 $type = 'none';
364 $activated = 'none';
365 if (isset($installed_plugins[$plugin['free']])) {
366 $type = 'free';
367 $activated = is_plugin_active($plugin['free']);
368 }
369 if (isset($installed_plugins[$plugin['pro']])) {
370 $type = 'pro';
371 $activated = is_plugin_active($plugin['pro']);
372 }
373 $plugins_list[$name]['activated'] = $activated;
374 $plugins_list[$name]['type'] = $type;
375 }
376
377 return [
378 'facebook' => [
379 'plugin' => $plugins_list['facebook']['pro'],
380 'link' => $plugins_list['facebook']['link'],
381 'download_plugin' => 'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip',
382 'title' => __('Custom Facebook Feed', 'reviews-feed'),
383 'description' => __('Add Facebook posts from your timeline, albums and much more.', 'reviews-feed'),
384 'icon' => 'fb-icon.svg',
385 'activated' => $plugins_list['facebook']['activated'],
386 'type' => $plugins_list['facebook']['type'],
387 ],
388 'instagram' => [
389 'plugin' => $plugins_list['instagram']['pro'],
390 'link' => $plugins_list['instagram']['link'],
391 'download_plugin' => 'https://downloads.wordpress.org/plugin/instagram-feed.zip',
392 'title' => __('Instagram Feed', 'reviews-feed'),
393 'description' => __('A quick and elegant way to add your Instagram posts to your website. ', 'reviews-feed'),
394 'icon' => 'insta-icon.svg',
395 'activated' => $plugins_list['instagram']['activated'],
396 'type' => $plugins_list['instagram']['type'],
397 ],
398 'twitter' => [
399 'plugin' => $plugins_list['twitter']['pro'],
400 'link' => $plugins_list['twitter']['link'],
401 'download_plugin' => 'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip',
402 'title' => __('Custom Twitter Feeds', 'reviews-feed'),
403 'description' => __('A customizable way to display tweets from your Twitter account. ', 'reviews-feed'),
404 'icon' => 'twitter-icon.svg',
405 'activated' => $plugins_list['twitter']['activated'],
406 'type' => $plugins_list['twitter']['type'],
407 ],
408 'youtube' => [
409 'plugin' => $plugins_list['youtube']['pro'],
410 'link' => $plugins_list['youtube']['link'],
411 'download_plugin' => 'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip',
412 'title' => __('Feeds for YouTube', 'reviews-feed'),
413 'description' => __('A simple yet powerful way to display videos from YouTube. ', 'reviews-feed'),
414 'icon' => 'youtube-icon.svg',
415 'activated' => $plugins_list['youtube']['activated'],
416 'type' => $plugins_list['youtube']['type'],
417 ]
418 ];
419 }
420
421 /**
422 * Get Smahballoon Recommended Plugins Info
423 *
424 * @since 1.0
425 */
426 public static function get_smashballoon_recommended_plugins_info()
427 {
428 $installed_plugins = get_plugins();
429 return [
430 'wpforms' => [
431 'plugin' => 'wpforms-lite/wpforms.php',
432 'download_plugin' => 'https://downloads.wordpress.org/plugin/wpforms-lite.zip',
433 'title' => __('WPForms', 'reviews-feed'),
434 'description' => __('The most beginner friendly drag & drop WordPress forms plugin allowing you to create beautiful contact forms, subscription forms, payment forms, and more in minutes, not hours!', 'reviews-feed'),
435 'icon' => 'plugin-wpforms.png',
436 'installed' => isset($installed_plugins['wpforms-lite/wpforms.php']),
437 'activated' => is_plugin_active('wpforms-lite/wpforms.php'),
438 ],
439 'monsterinsights' => [
440 'plugin' => 'google-analytics-for-wordpress/googleanalytics.php',
441 'download_plugin' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip',
442 'title' => __('MonsterInsights', 'reviews-feed'),
443 'description' => __('MonsterInsights makes it “effortless” to properly connect your WordPress site with Google Analytics, so you can start making data-driven decisions to grow your business.', 'reviews-feed'),
444 'icon' => 'plugin-mi.png',
445 'installed' => isset($installed_plugins['google-analytics-for-wordpress/googleanalytics.php']),
446 'activated' => is_plugin_active('google-analytics-for-wordpress/googleanalytics.php'),
447 ],
448 'optinmonster' => [
449 'plugin' => 'optinmonster/optin-monster-wp-api.php',
450 'download_plugin' => 'https://downloads.wordpress.org/plugin/optinmonster.zip',
451 'title' => __('OptinMonster', 'reviews-feed'),
452 'description' => __('Our high-converting optin forms like Exit-Intent® popups, Fullscreen Welcome Mats, and Scroll boxes help you dramatically boost conversions and get more email subscribers.', 'reviews-feed'),
453 'icon' => 'plugin-om.png',
454 'installed' => isset($installed_plugins['optinmonster/optin-monster-wp-api.php']),
455 'activated' => is_plugin_active('optinmonster/optin-monster-wp-api.php'),
456 ],
457 'wp_mail_smtp' => [
458 'plugin' => 'wp-mail-smtp/wp_mail_smtp.php',
459 'download_plugin' => 'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
460 'title' => __('WP Mail SMTP', 'reviews-feed'),
461 'description' => __('Make sure your website\'s emails reach the inbox. Our goal is to make email deliverability easy and reliable. Trusted by over 1 million websites.', 'reviews-feed'),
462 'icon' => 'plugin-smtp.png',
463 'installed' => isset($installed_plugins['wp-mail-smtp/wp_mail_smtp.php']),
464 'activated' => is_plugin_active('wp-mail-smtp/wp_mail_smtp.php'),
465 ],
466 'rafflepress' => [
467 'plugin' => 'rafflepress/rafflepress.php',
468 'download_plugin' => 'https://downloads.wordpress.org/plugin/rafflepress.zip',
469 'title' => __('RafflePress', 'reviews-feed'),
470 'description' => __('Turn your visitors into brand ambassadors! Easily grow your email list, website traffic, and social media followers with powerful viral giveaways & contests.', 'reviews-feed'),
471 'icon' => 'plugin-rp.png',
472 'installed' => isset($installed_plugins['rafflepress/rafflepress.php']),
473 'activated' => is_plugin_active('rafflepress/rafflepress.php'),
474 ],
475 'aioseo' => [
476 'plugin' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
477 'download_plugin' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip',
478 'title' => __('All in One SEO Pack', 'reviews-feed'),
479 'description' => __('Out-of-the-box SEO for WordPress. Features like XML Sitemaps, SEO for custom post types, SEO for blogs, business sites, or ecommerce sites, and much more.', 'reviews-feed'),
480 'icon' => 'plugin-seo.png',
481 'installed' => isset($installed_plugins['all-in-one-seo-pack/all_in_one_seo_pack.php']),
482 'activated' => is_plugin_active('all-in-one-seo-pack/all_in_one_seo_pack.php'),
483 ]
484 ];
485 }
486 /**
487 * Get other active plugins of Smash Balloon
488 *
489 * @since 4.4.0
490 */
491 public static function get_sb_active_plugins_info()
492 {
493 // get the WordPress's core list of installed plugins
494 if (!function_exists('get_plugins')) {
495 require_once ABSPATH . 'wp-admin/includes/plugin.php';
496 }
497 $installed_plugins = get_plugins();
498
499 $is_facebook_installed = false;
500 $facebook_plugin = 'custom-facebook-feed/custom-facebook-feed.php';
501 if (isset($installed_plugins['custom-facebook-feed-pro/custom-facebook-feed.php'])) {
502 $is_facebook_installed = true;
503 $facebook_plugin = 'custom-facebook-feed-pro/custom-facebook-feed.php';
504 } elseif (isset($installed_plugins['custom-facebook-feed/custom-facebook-feed.php'])) {
505 $is_facebook_installed = true;
506 }
507
508 $is_instagram_installed = false;
509 $instagram_plugin = 'instagram-feed/instagram-feed.php';
510 if (isset($installed_plugins['instagram-feed-pro/instagram-feed.php'])) {
511 $is_instagram_installed = true;
512 $instagram_plugin = 'instagram-feed-pro/instagram-feed.php';
513 } elseif (isset($installed_plugins['instagram-feed/instagram-feed.php'])) {
514 $is_instagram_installed = true;
515 }
516
517 $is_twitter_installed = false;
518 $twitter_plugin = 'custom-twitter-feeds/custom-twitter-feed.php';
519 if (isset($installed_plugins['custom-twitter-feeds-pro/custom-twitter-feed.php'])) {
520 $is_twitter_installed = true;
521 $twitter_plugin = 'custom-twitter-feeds-pro/custom-twitter-feed.php';
522 } elseif (isset($installed_plugins['custom-twitter-feeds/custom-twitter-feed.php'])) {
523 $is_twitter_installed = true;
524 }
525
526 $is_youtube_installed = false;
527 $youtube_plugin = 'feeds-for-youtube/youtube-feed.php';
528 if (isset($installed_plugins['youtube-feed-pro/youtube-feed-pro.php'])) {
529 $is_youtube_installed = true;
530 $youtube_plugin = 'youtube-feed-pro/youtube-feed-pro.php';
531 } elseif (isset($installed_plugins['feeds-for-youtube/youtube-feed.php'])) {
532 $is_youtube_installed = true;
533 }
534
535 $is_social_wall_installed = isset($installed_plugins['social-wall/social-wall.php']) ? true : false;
536 $social_wall_plugin = 'social-wall/social-wall.php';
537
538
539 return array(
540 'is_facebook_installed' => $is_facebook_installed,
541 'is_instagram_installed' => $is_instagram_installed,
542 'is_twitter_installed' => $is_twitter_installed,
543 'is_youtube_installed' => $is_youtube_installed,
544 'is_social_wall_installed' => $is_social_wall_installed,
545 'facebook_plugin' => $facebook_plugin,
546 'instagram_plugin' => $instagram_plugin,
547 'twitter_plugin' => $twitter_plugin,
548 'youtube_plugin' => $youtube_plugin,
549 'social_wall_plugin' => $social_wall_plugin,
550 'installed_plugins' => $installed_plugins
551 );
552 }
553
554 /**
555 * SBR Get Whitespace
556 *
557 * @since 1.0
558 *
559 * @param int $times
560 *
561 * @return string
562 */
563 public static function get_whitespace($times)
564 {
565 return str_repeat('&nbsp;', $times);
566 }
567
568 /**
569 * Get Site and Server Info
570 *
571 * @since 1.0
572 *
573 * @return string
574 */
575 public static function get_site_n_server_info()
576 {
577 $allow_url_fopen = ini_get('allow_url_fopen') ? "Yes" : "No";
578 $php_curl = is_callable('curl_init') ? "Yes" : "No";
579 $php_json_decode = function_exists("json_decode") ? "Yes" : "No";
580 $php_ssl = in_array('https', stream_get_wrappers()) ? "Yes" : "No";
581
582 $output = '## SITE/SERVER INFO: ##' . "</br>";
583 $output .= 'Plugin Version:' . self::get_whitespace(11) . SBR_MENU_SLUG . "</br>";
584 $output .= 'Site URL:' . self::get_whitespace(17) . site_url() . "</br>";
585 $output .= 'Home URL:' . self::get_whitespace(17) . home_url() . "</br>";
586 $output .= 'WordPress Version:' . self::get_whitespace(8) . get_bloginfo('version') . "</br>";
587 $output .= 'PHP Version:' . self::get_whitespace(14) . PHP_VERSION . "</br>";
588 $output .= 'Web Server Info:' . self::get_whitespace(10) . esc_html($_SERVER['SERVER_SOFTWARE']) . "</br>";
589 $output .= 'PHP allow_url_fopen:' . self::get_whitespace(6) . $allow_url_fopen . "</br>";
590 $output .= 'PHP cURL:' . self::get_whitespace(17) . $php_curl . "</br>";
591 $output .= 'JSON:' . self::get_whitespace(21) . $php_json_decode . "</br>";
592 $output .= 'SSL Stream:' . self::get_whitespace(15) . $php_ssl . "</br>";
593 $output .= "</br>";
594
595 return $output;
596 }
597
598 /**
599 * Get Active Plugins
600 *
601 * @since 1.0
602 *
603 * @return string
604 */
605 public static function get_active_plugins_info()
606 {
607 $plugins = get_plugins();
608 $active_plugins = get_option('active_plugins');
609 $output = "## ACTIVE PLUGINS: ## </br>";
610
611 foreach ($plugins as $plugin_path => $plugin) {
612 if (in_array($plugin_path, $active_plugins)) {
613 $output .= $plugin['Name'] . ': ' . $plugin['Version'] . "</br>";
614 }
615 }
616
617 $output .= "</br>";
618
619 return $output;
620 }
621
622
623 /**
624 * Get Global Settings
625 *
626 * @since 1.0
627 *
628 * @return string
629 */
630 public static function get_global_settings_info()
631 {
632 $output = '## GLOBAL SETTINGS: ## </br>';
633 $sbr_settings = get_option('sbr_settings', array());
634 if (! is_array($sbr_settings)) {
635 $sbr_settings = array();
636 }
637
638 $plugin_status = new AuthorizationStatusCheck();
639
640 $statuses = $plugin_status->get_statuses();
641
642 if (Util::sbr_is_pro()) {
643 $output .= 'License key: ';
644 if (isset($sbr_settings['license_key'])) {
645 $output .= esc_html($sbr_settings['license_key']);
646 } else {
647 $output .= ' Not added';
648 }
649
650 $output .= '</br>';
651 $output .= 'License Tier: ';
652 if (isset($statuses['license_tier'])) {
653 $output .= esc_html($statuses['license_tier']);
654 } else {
655 $output .= ' Not Set';
656 }
657 $output .= '</br>';
658 $output .= 'License status: ';
659 if (isset($sbr_settings['license_status'])) {
660 $output .= $sbr_settings['license_status'];
661 } else {
662 $output .= ' Inactive';
663 }
664 $output .= '</br>';
665 }
666 $output .= 'Preserve settings if plugin is removed: ';
667 $output .= isset($sbr_settings['preserve_settings']) && ($sbr_settings['preserve_settings']) ? 'Yes' : 'No';
668 $output .= '</br>';
669 $output .= 'Caching: ';
670 $output .= $statuses['license_tier'] === 3 ? 'Twice daily' : 'daily';
671
672 $output .= '</br>';
673 $output .= 'GDPR: ';
674 $output .= isset($sbr_settings['gdpr']) ? $sbr_settings['gdpr'] : ' Not setup';
675 $output .= '</br>';
676 $output .= 'Optimize Images: ';
677 $output .= isset($sbr_settings['optimize_images']) && $sbr_settings['optimize_images'] === true ? 'Enabled' : 'Disabled';
678 $output .= '</br>';
679 $output .= 'Usage Tracking: ';
680 $output .= isset($sbr_settings['usagetracking']) && $sbr_settings['usagetracking'] === true ? 'Enabled' : 'Disabled';
681 $output .= '</br>';
682 $output .= 'Enqueue in Head: ';
683 $output .= isset($sbr_settings['enqueue_js_in_header']) && $sbr_settings['enqueue_js_in_header'] === true ? 'Enabled' : 'Disabled';
684 $output .= '</br>';
685 $output .= 'Admin Error Notice: ';
686 $output .= isset($sbr_settings['admin_error_notices']) && $sbr_settings['admin_error_notices'] === true ? 'Enabled' : 'Disabled';
687 $output .= '</br>';
688 $output .= 'Feed Issue Email Reports: ';
689 $output .= isset($sbr_settings['feed_issue_reports']) && $sbr_settings['feed_issue_reports'] === true ? 'Enabled' : 'Disabled';
690 $output .= '</br>';
691 $output .= '</br>';
692 return $output;
693 }
694
695 /**
696 * Get Feeds Settings
697 *
698 * @since 1.0
699 *
700 * @return string
701 */
702 public static function get_sources_settings_info()
703 {
704 $output = '## SOURCES: ## </br>';
705 $source_list = SBR_Sources::get_sources_list();
706 foreach ($source_list as $feed) {
707 $output .= $feed['name'] . ' ( ' . strtoupper($feed['provider']) . ' => ' . $feed['account_id'] . ' )';
708 $output .= '</br>';
709 }
710 $output .= '</br>';
711
712 return $output;
713 }
714
715 /**
716 * Get Feeds Settings
717 *
718 * @since 1.0
719 *
720 * @return string
721 */
722 public static function get_api_settings_info()
723 {
724 $output = '## API KEYS: ## </br>';
725 $api_keys = get_option('sbr_apikeys', []);
726
727 foreach ($api_keys as $id => $api) {
728 $output .= ucfirst($id) . ' => ' . $api;
729 $output .= '</br>';
730 }
731 $output .= '</br>';
732
733 return $output;
734 }
735
736
737
738 /**
739 * Get Feeds Settings
740 *
741 * @since 1.0
742 *
743 * @return string
744 */
745 public static function get_feeds_settings_info()
746 {
747 $output = '## FEEDS: ## </br>';
748
749 $feeds_list = DB::get_feeds_list();
750
751 $i = 0;
752 foreach ($feeds_list as $feed) {
753 if ($i >= 25) {
754 break;
755 }
756 $output .= $feed['feed_name'];
757 if (isset($feed['settings'])) {
758 $output .= '</br>';
759 if (!empty($feed['sourcesList'])) {
760 foreach ($feed['sourcesList'] as $id => $source) {
761 $output .= esc_html($source['name']);
762 $output .= ' (' . esc_html(ucfirst($source['name'])) . ' => ' . esc_html($source['account_id']) . ')';
763 $output .= '</br>';
764 }
765 }
766 }
767 $output .= '</br>';
768 if (isset($feed['location_summary']) && count($feed['location_summary']) > 0) {
769 $first_feed = $feed['location_summary'][0];
770 if (!empty($first_feed['link'])) {
771 $output .= esc_html($first_feed['link']) . '?sb_debug';
772 $output .= '</br>';
773 }
774 }
775
776 if ($i < (count($feeds_list) - 1)) {
777 $output .= '</br>';
778 }
779 $i++;
780 }
781 $output .= '</br>';
782
783 return $output;
784 }
785
786 /**
787 * Get Posts Table Info
788 *
789 * @since 1.0
790 *
791 * @return string
792 */
793 public static function get_posts_table_info()
794 {
795 $output = '## POSTS: ## </br>';
796
797 global $wpdb;
798 $table_name = $wpdb->prefix . 'sbr_posts';
799 $feeds_posts_table_name = $wpdb->prefix . 'sbr_reviews_posts';
800
801 if ($wpdb->get_var("show tables like '$feeds_posts_table_name'") !== $feeds_posts_table_name) {
802 $output .= 'no feeds posts table</br>';
803 } else {
804 $last_result = $wpdb->get_results("SELECT * FROM $feeds_posts_table_name ORDER BY id DESC LIMIT 1;");
805 if (is_array($last_result) && isset($last_result[0])) {
806 $output .= '## FEEDS POSTS TABLE ##</br>';
807 foreach ($last_result as $column) {
808 foreach ($column as $key => $value) {
809 $output .= esc_html($key) . ': ' . esc_html($value) . '</br>';
810 }
811 }
812 } else {
813 $output .= 'feeds posts has no rows';
814 $output .= '</br>';
815 }
816 }
817 $output .= '</br>';
818 if ($wpdb->get_var("show tables like '$table_name'") !== $table_name) {
819 $output .= 'no posts table</br>';
820 } else {
821 $last_result = $wpdb->get_results("SELECT * FROM $table_name ORDER BY id DESC LIMIT 1;");
822 if (is_array($last_result) && isset($last_result[0])) {
823 $output .= '## POSTS TABLE ##';
824 $output .= '</br>';
825 foreach ($last_result as $column) {
826 foreach ($column as $key => $value) {
827 $output .= esc_html($key) . ': ' . esc_html($value) . '</br>';
828 }
829 }
830 } else {
831 $output .= 'posts has no rows</br>';
832 }
833 }
834 $output .= '</br>';
835
836 return $output;
837 }
838
839 /**
840 * List of possible languages/translations
841 *
842 * @since 1.0
843 *
844 * @return array
845 */
846 public static function get_translation_languages($include_default = false)
847 {
848 $languages = [
849 'default' => 'Default',
850 '' => 'No Translation',
851 'en' => 'English',
852 'af' => 'Afrikaans',
853 'am' => 'Amharic',
854 'ar' => 'Arabic',
855 'az' => 'Azerbaijani',
856 'be' => 'Belarusian',
857 'bg' => 'Bulgarian',
858 'bn' => 'Bengali',
859 'bs' => 'Bosnian',
860 'ca' => 'Catalan',
861 'cs' => 'Czech',
862 'da' => 'Danish',
863 'de' => 'German',
864 'el' => 'Greek',
865 'en-AU' => 'English (Australian)',
866 'en-GB' => 'English (Great Britain)',
867 'es' => 'Spanish',
868 'es-419' => 'Spanish (Latin America)',
869 'et' => 'Estonian',
870 'eu' => 'Basque',
871 'fa' => 'Farsi',
872 'fi' => 'Finnish',
873 'fil' => 'Filipino',
874 'fr' => 'French',
875 'fr-CA' => 'French (Canada)',
876 'gl' => 'Galician',
877 'gu' => 'Gujarati',
878 'hi' => 'Hindi',
879 'hr' => 'Croatian',
880 'hu' => 'Hungarian',
881 'hy' => 'Armenian',
882 'id' => 'Indonesian',
883 'is' => 'Icelandic',
884 'it' => 'Italian ',
885 'iw' => 'Hebrew',
886 'ja' => 'Japanese',
887 'ka' => 'Georgian',
888 'kk' => 'Kazakh',
889 'km' => 'Khmer',
890 'kn' => 'Kannada',
891 'ko' => 'Korean',
892 'ky' => 'Kyrgyz',
893 'lo' => 'Lao',
894 'lt' => 'Lithuanian',
895 'lv' => 'Latvian',
896 'mk' => 'Macedonian',
897 'ml' => 'Malayalam',
898 'mn' => 'Mongolian',
899 'mr' => 'Marathi',
900 'ms' => 'Malay',
901 'my' => 'Burmese',
902 'ne' => 'Nepali',
903 'nl' => 'Dutch',
904 'no' => 'Norwegian',
905 'pa' => 'Punjabi',
906 'pl' => 'Polish',
907 'pt' => 'Portuguese',
908 'pt-BR' => 'Portuguese (Brazil)',
909 'pt-PT' => 'Portuguese (Portugal)',
910 'ro' => 'Romanian',
911 'ru' => 'Russian',
912 'si' => 'Sinhalese',
913 'sk' => 'Slovak',
914 'sl' => 'Slovenian',
915 'sq' => 'Albanian',
916 'sr' => 'Serbian',
917 'sv' => 'Swedish',
918 'sw' => 'Swahili',
919 'ta' => 'Tamil',
920 'te' => 'Telugu',
921 'th' => 'Thai',
922 'tr' => 'Turkish',
923 'uk' => 'Ukrainian',
924 'ur' => 'Urdu',
925 'uz' => 'Uzbek',
926 'vi' => 'Vietnamese',
927 'zh' => 'Chinese',
928 'zh-CN' => 'Chinese (Simplified)',
929 'zh-HK' => 'Chinese (Hong Kong)',
930 'zh-TW' => 'Chinese (Traditional)',
931 'zu' => 'Zulu'
932 ];
933 if ($include_default === false) {
934 array_shift($languages);
935 }
936
937 //Detect if WPMl is active then add the option
938 if (defined('ICL_SITEPRESS_VERSION') && Util::sbr_is_pro()) {
939 $position = $include_default ? 2 : 1;
940 $languages = array_merge(
941 array_slice($languages, 0, $position),
942 ['wpml' => 'Automatically by WPML'],
943 array_slice($languages, $position)
944 );
945 }
946
947 return $languages;
948 }
949
950 /**
951 * Get Language for API call
952 *
953 * @since 1.0
954 *
955 * @return string
956 */
957
958 public static function get_settings_language($settings)
959 {
960 $args = isset($settings['localization']) && $settings['localization'] !== 'default'
961 ? $settings
962 : wp_parse_args(get_option('sbr_settings', []), sbr_plugin_settings_defaults());
963
964 return $args['localization'];
965 }
966
967 /**
968 * Get Language for API call
969 *
970 * @since 1.0
971 *
972 * @return string
973 */
974 public static function get_api_call_language($settings)
975 {
976 $language = Util::sbr_is_pro()
977 ? \SmashBalloon\Reviews\Pro\Helpers\SBR_WPML::get_current_language(Util::get_settings_language($settings))
978 : Util::get_settings_language($settings);
979
980 /**
981 * Filter the language code sent to the provider API (e.g. Google Places).
982 * Lets a site map an unsupported locale to a supported one, e.g. a WPML
983 * `es-mx` site forcing `es-419` for Latin American Spanish. SMASH-1617.
984 * Read the current WPML language inside the callback (apply_filters
985 * 'wpml_current_language') if you need per-page context.
986 *
987 * @since 2.6.6
988 * @param string $language Resolved language code (or 'default').
989 */
990 return apply_filters('sbr_google_api_language', $language);
991 }
992
993 /**
994 * Map a locale / WPML language code to a code the Google Places API accepts.
995 *
996 * Google only honours codes from its supported list (get_translation_languages);
997 * an unsupported value (e.g. WPML's `es-mx`) makes Google return reviews
998 * untranslated. This normalises common regional codes to a supported one.
999 * Spanish is special-cased: Spain dialects -> `es`, any other Spanish variant
1000 * (es-mx, es-ar, …) -> `es-419` (Latin America) — region-stripping alone would
1001 * wrongly land on Spain Spanish. Returns null when nothing supported matches,
1002 * so callers can fall back (never send a bogus code). SMASH-1617.
1003 *
1004 * @since 2.6.6
1005 * @param string|null $code Raw locale / WPML code (e.g. 'es-mx', 'pt-br', 'es_MX').
1006 * @return string|null A Google-supported code, or null if unmappable.
1007 */
1008 public static function map_wpml_to_google_language($code)
1009 {
1010 if (! is_string($code) || $code === '') {
1011 return null;
1012 }
1013
1014 $allowed = self::get_translation_languages();
1015
1016 // Already a supported code (exact match) — keep existing behaviour.
1017 if (isset($allowed[$code]) && $code !== 'default' && $code !== '') {
1018 return $code;
1019 }
1020
1021 $norm = strtolower(str_replace('_', '-', $code));
1022
1023 // Spanish: Spain -> es; every other variant (es-mx, es-ar, …) -> es-419.
1024 if ($norm === 'es' || $norm === 'es-es') {
1025 return 'es';
1026 }
1027 if (strpos($norm, 'es-') === 0) {
1028 return 'es-419';
1029 }
1030
1031 // Case-insensitive match for region variants Google does list (pt-br -> pt-BR).
1032 foreach (array_keys($allowed) as $supported) {
1033 if (strtolower($supported) === $norm) {
1034 return $supported;
1035 }
1036 }
1037
1038 // Strip the region and fall back to the base language if supported (fr-fr -> fr).
1039 $base = strtok($norm, '-');
1040 if ($base !== false && isset($allowed[$base])) {
1041 return $base;
1042 }
1043
1044 return null;
1045 }
1046
1047 /**
1048 * Is Plugin Pro
1049 *
1050 * @since 1.0
1051 *
1052 * @return boolean
1053 */
1054 public static function sbr_is_pro()
1055 {
1056 return defined('SBR_PRO') && SBR_PRO === true;
1057 }
1058
1059 /**
1060 * Check if user has Pro Plus tier
1061 *
1062 * @since 2.5.0
1063 *
1064 * @return boolean
1065 */
1066 public static function sbr_is_pro_plus()
1067 {
1068 if (!self::sbr_is_pro()) {
1069 return false;
1070 }
1071
1072 // Read from sbr_statuses where license_tier is written during license activation
1073 // This is consistent with AuthorizationStatusCheck::get_license_tier()
1074 $statuses = get_option('sbr_statuses', []);
1075 $license_tier = $statuses['license_tier'] ?? 0;
1076
1077 // Handle All Access Bundle special case (same as AuthorizationStatusCheck)
1078 if (
1079 isset($statuses['license_info']['item_name'])
1080 && $statuses['license_info']['item_name'] === 'All Access Bundle - All Plugins Unlimited'
1081 ) {
1082 $license_tier = 3;
1083 }
1084
1085 // Tiers: 1 = Pro Basic, 2 = Pro Plus, 3 = Elite
1086 // Pro Plus and Elite (tiers 2 and 3) have full access
1087 return (int) $license_tier >= 2;
1088 }
1089
1090
1091 /**
1092 * Get List of Upsell Modal Content
1093 *
1094 * @since 1.0
1095 *
1096 * @return array
1097 */
1098 public static function upsell_modal_content()
1099 {
1100 // Pro users (including Pro Plus) don't need provider upsell modals
1101 // Note: Review Alert Pro Plus upsells are handled separately in SBR_ReviewAlert_Builder
1102 if (Util::sbr_is_pro()) {
1103 return [];
1104 }
1105
1106 // Free users get all upsell modals
1107 return [
1108 'facebookProvider' => [
1109 'heading' => __('Upgrade to Pro to display Facebook reviews', 'reviews-feed'),
1110 'description' => __('Upgrade to our "Plus" tier to display reviews from the well known social media platform.', 'reviews-feed'),
1111 'image' => 'upsell-facebook.png',
1112 'buttons' => [
1113 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=facebook-modal&utm_content=LiteUsers50OFF',
1114 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=facebook-modal&utm_content=Upgrade',
1115 # 'demo' => 'https://smashballoon.com/reviews-feed/demo/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=responsive-modal&utm_content=ViewDemo'
1116 ],
1117
1118 'includeContent' => true
1119 ],
1120 'trustpilotProvider' => [
1121 'heading' => __('Upgrade to Pro to display TrustPilot reviews', 'reviews-feed'),
1122 'description' => __('Upgrade to our "Plus" tier to display reviews from the well known business review site.', 'reviews-feed'),
1123 'image' => 'upsell-trustpilot.png',
1124 'buttons' => [
1125 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=trustpilot-modal&utm_content=LiteUsers50OFF',
1126 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=trustpilot-modal&utm_content=Upgrade'
1127 ],
1128 'includeContent' => true
1129 ],
1130 'tripadvisorProvider' => [
1131 'heading' => __('Upgrade to Pro to display TripAdvisor reviews', 'reviews-feed'),
1132 'description' => __('Upgrade to our "Elite" tier to display reviews from the well known travel advice site.', 'reviews-feed'),
1133 'image' => 'upsell-tripadvisor.png',
1134 'buttons' => [
1135 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=tripadvisor-modal&utm_content=LiteUsers50OFF',
1136 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=tripadvisor-modal&utm_content=Upgrade'
1137 ],
1138 'includeContent' => true
1139 ],
1140 'wordpress.orgProvider' => [
1141 'heading' => __('Upgrade to Pro to display WordPress.org reviews', 'reviews-feed'),
1142 'description' => __('Upgrade to our "Elite" tier to display reviews for plugins and themes.', 'reviews-feed'),
1143 'image' => 'upsell-wordpress.org.png',
1144 'buttons' => [
1145 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=wordpressorg-modal&utm_content=LiteUsers50OFF',
1146 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=wordpressorg-modal&utm_content=Upgrade'
1147 ],
1148 'includeContent' => true
1149 ],
1150 'woocommerceProvider' => [
1151 'heading' => __('Upgrade to Pro to display WooCommerce reviews', 'reviews-feed'),
1152 'description' => __('Upgrade to our "Plus" tier to display product reviews from your WooCommerce store.', 'reviews-feed'),
1153 'image' => 'upsell-woocommerce.png',
1154 'buttons' => [
1155 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=woocommerce-modal&utm_content=LiteUsers50OFF',
1156 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=woocommerce-modal&utm_content=Upgrade'
1157 ],
1158 'includeContent' => true
1159 ],
1160 'eddProvider' => [
1161 'heading' => __('Upgrade to Pro to display Easy Digital Downloads reviews', 'reviews-feed'),
1162 'description' => __('Upgrade to our "Plus" tier to display download reviews from your EDD store.', 'reviews-feed'),
1163 'image' => 'upsell-woocommerce.png',
1164 'buttons' => [
1165 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=edd-modal&utm_content=LiteUsers50OFF',
1166 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=edd-modal&utm_content=Upgrade'
1167 ],
1168 'includeContent' => true
1169 ],
1170 'airbnbProvider' => [
1171 'heading' => __('Upgrade to Pro to display Airbnb reviews', 'reviews-feed'),
1172 'description' => __('Upgrade to our "Plus" tier to display reviews from the popular accommodation platform.', 'reviews-feed'),
1173 'image' => 'upsell-airbnb.png',
1174 'buttons' => [
1175 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=airbnb-modal&utm_content=LiteUsers50OFF',
1176 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=airbnb-modal&utm_content=Upgrade'
1177 ],
1178 'includeContent' => true
1179 ],
1180 'bookingProvider' => [
1181 'heading' => __('Upgrade to Pro to display Booking.com reviews', 'reviews-feed'),
1182 'description' => __('Upgrade to our "Plus" tier to display reviews from the leading travel booking site.', 'reviews-feed'),
1183 'image' => 'upsell-booking.png',
1184 'buttons' => [
1185 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=booking-modal&utm_content=LiteUsers50OFF',
1186 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=booking-modal&utm_content=Upgrade'
1187 ],
1188 'includeContent' => true
1189 ],
1190 'aliexpressProvider' => [
1191 'heading' => __('Upgrade to Pro to display AliExpress reviews', 'reviews-feed'),
1192 'description' => __('Upgrade to our "Plus" tier to display product reviews from the global marketplace.', 'reviews-feed'),
1193 'image' => 'upsell-aliexpress.png',
1194 'buttons' => [
1195 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=aliexpress-modal&utm_content=LiteUsers50OFF',
1196 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=aliexpress-modal&utm_content=Upgrade'
1197 ],
1198 'includeContent' => true
1199 ],
1200 'carouselModal' => [
1201 'heading' => __('Upgrade to Pro to get Carousel layout', 'reviews-feed'),
1202 'description' => __('An eye-catching rotating slider of your videos to add extra content in minimal space on your website.', 'reviews-feed'),
1203 'image' => 'upsell-carousel.png',
1204 'buttons' => [
1205 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=carousel-modal&utm_content=LiteUsers50OFF',
1206 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=carousel-modal&utm_content=Upgrade'
1207 ],
1208 'includeContent' => true
1209 ],
1210 'moreReviewsModal' => [
1211 'heading' => __('Upgrade to Pro to display more reviews', 'reviews-feed'),
1212 'description' => __('More layout settings to customize the look and feel of your reviews even more.', 'reviews-feed'),
1213 'image' => 'upsell-morereviews.png',
1214 'buttons' => [
1215 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=num-reviews-modal&utm_content=LiteUsers50OFF',
1216 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=num-reviews-modal&utm_content=Upgrade'
1217 ],
1218 'includeContent' => true
1219 ],
1220 'averageRatingModal' => [
1221 'heading' => __('Upgrade to Pro to display average rating', 'reviews-feed'),
1222 'description' => __('Boost social proof to make more sales conversions with the number of ratings and an average rating.', 'reviews-feed'),
1223 'image' => 'upsell-averagerating.png',
1224 'buttons' => [
1225 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=average-rating-modal&utm_content=LiteUsers50OFF',
1226 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=average-rating-modal&utm_content=Upgrade'
1227 ],
1228 'includeContent' => true
1229 ],
1230 'loadMoreModal' => [
1231 'heading' => __('Upgrade to Pro to add load more functionality', 'reviews-feed'),
1232 'description' => __('Overwhelm (in a good way) your visitors with additional reviews loaded on the page with a click.', 'reviews-feed'),
1233 'image' => 'upsell-loadmore.png',
1234 'buttons' => [
1235 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=load-more-modal&utm_content=LiteUsers50OFF',
1236 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=load-more-modal&utm_content=Upgrade'
1237 ],
1238 'includeContent' => true
1239 ],
1240 'reviewsMediaModal' => [
1241 'heading' => __('Upgrade to Pro to add images', 'reviews-feed'),
1242 'description' => __('Display images from Yelp and Tripadvisor reviews.', 'reviews-feed'),
1243 'image' => 'upsell-reviewsmedia.png',
1244 'buttons' => [
1245 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=lite-upgrade-footer-coupon&utm_content=LiteUsers50OFF',
1246 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=template-modal&utm_content=Upgrade'
1247 ],
1248 'includeContent' => true
1249 ],
1250 'authorImageModal' => [
1251 'heading' => __('Upgrade to Pro to display author images', 'reviews-feed'),
1252 'description' => __('Build brand trust with positive reviews from real customers.', 'reviews-feed'),
1253 'image' => 'upsell-authorimage.png',
1254 'buttons' => [
1255 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=author-avatar-modal&utm_content=LiteUsers50OFF',
1256 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=author-avatar-modal&utm_content=Upgrade'
1257 ],
1258 'includeContent' => true
1259 ],
1260 'filtersModal' => [
1261 'heading' => __('Upgrade to Pro to filter your reviews', 'reviews-feed'),
1262 'description' => __('Show only the most positive reviews and build brand trust with review filtering.', 'reviews-feed'),
1263 'image' => 'upsell-filters.png',
1264 'buttons' => [
1265 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=star-filter-modal&utm_content=LiteUsers50OFF',
1266 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=star-filter-modal&utm_content=Upgrade'
1267 ],
1268 'includeContent' => true
1269 ],
1270 'moderationModal' => [
1271 'heading' => __('Upgrade to Pro to moderate your reviews', 'reviews-feed'),
1272 'description' => __('Take complete control of what reviews show in the feed using keyword filters and a visual moderation system.', 'reviews-feed'),
1273 'image' => 'upsell-moderation.png',
1274 'buttons' => [
1275 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=moderation-modal&utm_content=LiteUsers50OFF',
1276 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=moderation-modal&utm_content=Upgrade'
1277 ],
1278 'includeContent' => true
1279 ],
1280 'templateModal' => [
1281 'heading' => __('Upgrade to Pro to get one-click templates!', 'reviews-feed'),
1282 'description' => __('Quickly create and preview new feeds with pre-configured options based on popular feed types.', 'reviews-feed'),
1283 'image' => 'upsell-template.png',
1284 'buttons' => [
1285 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=template-modal&utm_content=LiteUsers50OFF',
1286 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=template-modal&utm_content=Upgrade'
1287 ],
1288 'includeContent' => true
1289 ],
1290 'responsiveModal' => [
1291 'heading' => __('Upgrade to Pro for responsive layouts', 'reviews-feed'),
1292 'description' => __('Take control of your feed layouts by customizing number of reviews & columns', 'reviews-feed'),
1293 'image' => 'upsell-responsive.png',
1294 'buttons' => [
1295 'lite' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=all-feeds&utm_medium=responsive-modal&utm_content=LiteUsers50OFF',
1296 'upgrade' => 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=customizer&utm_medium=responsive-modal&utm_content=Upgrade'
1297 ],
1298 'includeContent' => true
1299 ],
1300 // Review Alert Upsell Modals
1301 'reviewAlertFeature' => [
1302 'heading' => __('Upgrade to Pro for Review Alerts', 'reviews-feed'),
1303 'description' => __('Display eye-catching review popups that cycle through your best reviews to boost social proof and conversions.', 'reviews-feed'),
1304 'image' => 'upsell-review-alert.png',
1305 'buttons' => [
1306 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-feature-modal&utm_content=LiteUsers50OFF',
1307 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-feature-modal&utm_content=Upgrade'
1308 ],
1309 'includeContent' => true
1310 ],
1311 'reviewAlertRecentReviews' => [
1312 'heading' => __('Upgrade to Pro for Recent Reviews popup', 'reviews-feed'),
1313 'description' => __('Cycle through your individual reviews one by one to showcase real customer feedback and build trust.', 'reviews-feed'),
1314 'image' => 'upsell-review-alert.png',
1315 'buttons' => [
1316 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-recent-reviews-modal&utm_content=LiteUsers50OFF',
1317 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-recent-reviews-modal&utm_content=Upgrade'
1318 ],
1319 'includeContent' => true
1320 ],
1321 'reviewAlertVariations' => [
1322 'heading' => __('Upgrade to Pro for more popup styles', 'reviews-feed'),
1323 'description' => __('Unlock V2 and V3 style variations for your notification popups with unique layouts and designs.', 'reviews-feed'),
1324 'image' => 'upsell-review-alert.png',
1325 'buttons' => [
1326 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-variations-modal&utm_content=LiteUsers50OFF',
1327 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-variations-modal&utm_content=Upgrade'
1328 ],
1329 'includeContent' => true
1330 ],
1331 'reviewAlertDarkTheme' => [
1332 'heading' => __('Upgrade to Pro for dark theme popups', 'reviews-feed'),
1333 'description' => __('Match your site\'s dark mode with elegant dark-themed notification popups.', 'reviews-feed'),
1334 'image' => 'upsell-review-alert.png',
1335 'buttons' => [
1336 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-dark-theme-modal&utm_content=LiteUsers50OFF',
1337 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-dark-theme-modal&utm_content=Upgrade'
1338 ],
1339 'includeContent' => true
1340 ],
1341 'reviewAlertCustomColor' => [
1342 'heading' => __('Upgrade to Pro for custom popup colors', 'reviews-feed'),
1343 'description' => __('Customize your popup accent color to perfectly match your brand identity.', 'reviews-feed'),
1344 'image' => 'upsell-review-alert.png',
1345 'buttons' => [
1346 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-custom-color-modal&utm_content=LiteUsers50OFF',
1347 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-custom-color-modal&utm_content=Upgrade'
1348 ],
1349 'includeContent' => true
1350 ],
1351 'reviewAlertMultiple' => [
1352 'heading' => __('Upgrade to Pro Plus for multiple popups', 'reviews-feed'),
1353 'description' => __('Create unlimited notification popups and display different popups on different pages.', 'reviews-feed'),
1354 'image' => 'upsell-review-alert.png',
1355 'buttons' => [
1356 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-multiple-modal&utm_content=LiteUsers50OFF',
1357 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-multiple-modal&utm_content=Upgrade'
1358 ],
1359 'includeContent' => true
1360 ],
1361 'reviewAlertTargeting' => [
1362 'heading' => __('Upgrade to Pro for page targeting', 'reviews-feed'),
1363 'description' => __('Control exactly where your notification popups appear with specific page targeting and exclusions.', 'reviews-feed'),
1364 'image' => 'upsell-review-alert.png',
1365 'buttons' => [
1366 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-targeting-modal&utm_content=LiteUsers50OFF',
1367 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-targeting-modal&utm_content=Upgrade'
1368 ],
1369 'includeContent' => true
1370 ],
1371 'reviewAlertBranding' => [
1372 'heading' => __('Remove Smash Balloon Branding', 'reviews-feed'),
1373 'description' => __('Present a clean, professional look by removing the \'Powered by\' badge from your notification popups.', 'reviews-feed'),
1374 'image' => 'upsell-review-alert.png',
1375 'buttons' => [
1376 'lite' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-branding-modal&utm_content=LiteUsers50OFF',
1377 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-branding-modal&utm_content=Upgrade',
1378 'learnMore' => 'https://smashballoon.com/reviews-feed/?utm_campaign=reviews-free&utm_source=review-alert&utm_medium=popup-branding-modal&utm_content=LearnMore'
1379 ],
1380 'includeContent' => true
1381 ]
1382 ];
1383 }
1384
1385 /**
1386 * Get Pro Plus upsell content for Pro users
1387 *
1388 * Returns upsell modals for features that require Pro Plus tier.
1389 *
1390 * @since 2.5.0
1391 *
1392 * @return array
1393 */
1394 public static function get_pro_plus_upsell_content()
1395 {
1396 return [
1397 'reviewAlertMultiple' => [
1398 'heading' => __('Upgrade to Pro Plus for multiple popups', 'reviews-feed'),
1399 'description' => __('Create unlimited notification popups and display different popups on different pages.', 'reviews-feed'),
1400 'image' => 'upsell-review-alert.png',
1401 'buttons' => [
1402 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-pro&utm_source=review-alert&utm_medium=popup-multiple-modal&utm_content=UpgradeToProPlus'
1403 ],
1404 'includeContent' => false
1405 ],
1406 'reviewAlertTargeting' => [
1407 'heading' => __('Upgrade to Pro Plus for page targeting', 'reviews-feed'),
1408 'description' => __('Control exactly where your notification popups appear with specific page targeting and exclusions.', 'reviews-feed'),
1409 'image' => 'upsell-review-alert.png',
1410 'buttons' => [
1411 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-pro&utm_source=review-alert&utm_medium=popup-targeting-modal&utm_content=UpgradeToProPlus'
1412 ],
1413 'includeContent' => false
1414 ],
1415 'reviewAlertBranding' => [
1416 'heading' => __('Remove Smash Balloon Branding', 'reviews-feed'),
1417 'description' => __('Present a clean, professional look by removing the \'Powered by\' badge from your notification popups.', 'reviews-feed'),
1418 'image' => 'upsell-review-alert.png',
1419 'buttons' => [
1420 'upgrade' => 'https://smashballoon.com/pricing/reviews-feed/?utm_campaign=reviews-pro&utm_source=review-alert&utm_medium=popup-branding-modal&utm_content=UpgradeToProPlus',
1421 'learnMore' => 'https://smashballoon.com/reviews-feed/?utm_campaign=reviews-pro&utm_source=review-alert&utm_medium=popup-branding-modal&utm_content=LearnMore'
1422 ],
1423 'includeContent' => true
1424 ]
1425 ];
1426 }
1427
1428 /**
1429 * Get List of Upsell Sidebar Cards
1430 *
1431 * @since 1.1
1432 *
1433 * @return array
1434 */
1435 public static function sidebar_upsell_cards()
1436 {
1437 if (Util::sbr_is_pro()) {
1438 return [];
1439 }
1440 return [
1441 [
1442 'heading' => __('Display images', 'reviews-feed'),
1443 'description' => __('With the Pro version enable images like avatars and review photos', 'reviews-feed'),
1444 'image' => 'upswidget-images.png',
1445 'modal' => 'authorImageModal'
1446 ],
1447 [
1448 'heading' => __('Get carousel layout', 'reviews-feed'),
1449 'description' => __('Show your reviews in a neat carousel with Review Feed Pro', 'reviews-feed'),
1450 'image' => 'upswidget-carousel.png',
1451 'modal' => 'carouselModal'
1452 ],
1453 [
1454 'heading' => __('Change your feed style with one-click templates!', 'reviews-feed'),
1455 'description' => __('Over 12 templates to choose from', 'reviews-feed'),
1456 'image' => 'upswidget-templates.png',
1457 'modal' => 'templateModal'
1458 ],
1459 [
1460 'heading' => __('Only show selected reviews with Moderation mode', 'reviews-feed'),
1461 'description' => __('Hide or show only certain reviews', 'reviews-feed'),
1462 'image' => 'upswidget-moderation.png',
1463 'modal' => 'moderationModal'
1464 ]
1465 ];
1466 }
1467
1468
1469 /**
1470 * Coerce a review's `provider`, `reviewer` and `source` into the array shapes
1471 * every reader expects ($review['provider']['name'], $review['reviewer']['name'],
1472 * $review['source']['id'], …). The relay can emit these as scalars (e.g.
1473 * `provider` => 'google', or an error-shaped payload yielding a scalar
1474 * `reviewer`/`source`), and PHP 7-era caches stored them that way in
1475 * `json_data`. On PHP 8.0+ a direct nested read on a scalar fatals with
1476 * "Cannot access offset of type string on string".
1477 *
1478 * This is the single source of truth used by SinglePostCache (both
1479 * constructors) and every raw/DB-decoded read site: the dedup key build in
1480 * PostAggregator::remove_duplicated_posts_list (front-end render, Feed.php:172)
1481 * reads source['id'] + reviewer['name'] + provider['name'] off the raw post,
1482 * parse_single_review reads reviewer/provider, and
1483 * SBR_Feed_Saver_Manager::duplicate_collection reads reviewer/source.
1484 *
1485 * SMASH-1578 guarded whole-review + source shapes at the cache loops, SMASH-1587
1486 * added provider; this also covers a scalar reviewer/source that the
1487 * is_array($single_review)-only cache guard lets through to store()/dedup
1488 * (reachable on WPSA-63160's associative-keyed payload). Healthy array data is
1489 * left intact — missing keys are filled with empty strings (via array union),
1490 * so the dedup key degrades to an empty segment instead of crashing.
1491 *
1492 * @param mixed $review
1493 * @return mixed Untouched when not an array (callers' is_array guards handle that).
1494 */
1495 public static function normalize_review_shape($review)
1496 {
1497 if (!is_array($review)) {
1498 return $review;
1499 }
1500
1501 if (isset($review['provider']) && is_string($review['provider'])) {
1502 $review['provider'] = ['name' => $review['provider']];
1503 } elseif (!isset($review['provider']) || !is_array($review['provider'])) {
1504 $review['provider'] = ['name' => ''];
1505 }
1506 if (!isset($review['reviewer']) || !is_array($review['reviewer'])) {
1507 $review['reviewer'] = [];
1508 }
1509 if (!isset($review['source']) || !is_array($review['source'])) {
1510 $review['source'] = [];
1511 }
1512 // Image containers iterated by resize_images() + add_local_image_urls().
1513 // A scalar here would fatal the foreach on PHP 8. Coerce a present
1514 // non-array to [] (leave absent untouched — they're optional). Element
1515 // shape is guarded at the loop sites (a flat URL-string element).
1516 foreach (['media', 'reviews_photos'] as $image_key) {
1517 if (isset($review[$image_key]) && !is_array($review[$image_key])) {
1518 $review[$image_key] = [];
1519 }
1520 }
1521
1522 // Guarantee the exact keys every reader assumes are present and string,
1523 // so a scalar/partial provider, reviewer or source degrades to empty
1524 // segments instead of "Cannot access offset…" fatals or "Undefined array
1525 // key" notices in the dedup key build / parse_single_review / preview
1526 // backfill (SMASH-1587 + PR #484 Copilot review + WPSA-63160 follow-up).
1527 $review['provider'] += ['name' => ''];
1528 $review['reviewer'] += ['name' => '', 'avatar' => ''];
1529 $review['source'] += ['id' => '', 'url' => ''];
1530 if (!is_string($review['provider']['name'])) {
1531 $review['provider']['name'] = '';
1532 }
1533 if (!is_string($review['reviewer']['name'])) {
1534 $review['reviewer']['name'] = '';
1535 }
1536 if (!is_string($review['source']['id'])) {
1537 // Preserve a numeric id (cast), but never (string)-cast an array/object —
1538 // that would emit an "Array to string conversion" notice (PR #482 Copilot).
1539 $review['source']['id'] = is_scalar($review['source']['id'])
1540 ? (string) $review['source']['id']
1541 : '';
1542 }
1543
1544 return $review;
1545 }
1546
1547 /**
1548 * Transform Single Review for storing purposes
1549 *
1550 * @since 1.4
1551 *
1552 * @return array
1553 */
1554 public static function parse_single_review($review, $provider_id, $review_id)
1555 {
1556 // Provider can arrive as a scalar slug (SMASH-1587); normalize before the
1557 // $review['provider']['name'] read below so it can't fatal on PHP 8.
1558 $review = self::normalize_review_shape($review);
1559 $name = $review['reviewer']['name'];
1560 $name_array = explode(' ', $name);
1561 $first_name = isset($review['reviewer']['first_name']) ? $review['reviewer']['first_name'] : $name_array[0];
1562 $last_name = isset($review['reviewer']['last_name']) ? $review['reviewer']['last_name'] : (isset($name_array[1]) ? $name_array[1] : '');
1563
1564 $sanitized_review = [
1565 'time' => $review['time'],
1566 'rating' => $review['rating'],
1567 'provider_id' => $provider_id,
1568 'review_id' => $review_id,
1569 'text' => $review['text'],
1570 'title' => isset($review['title']) ? $review['title'] : substr($review['text'], 0, 40),
1571 'reviewer' => [
1572 'name' => $name,
1573 'first_name' => $first_name,
1574 'last_name' => $last_name,
1575 'avatar' => $review['reviewer']['avatar']
1576 ],
1577 'provider' => [
1578 'name' => $review['provider']['name']
1579 ],
1580 'source' => [
1581 'id' => $provider_id,
1582 'url' => ''
1583 ]
1584 ];
1585 return $sanitized_review;
1586 }
1587
1588
1589 public static function is_facebook_collection_post($post)
1590 {
1591 return ( isset($post['provider']) && isset($post['provider']['name']) && $post['provider']['name'] === 'facebook' && isset($post['provider_id']) && strpos($post['provider_id'], 'collection') !== false ) === true;
1592 }
1593
1594 /**
1595 * Convert Object to Array
1596 *
1597 * @return array
1598 *
1599 * @since 1.0
1600 */
1601 public static function object_to_array($data)
1602 {
1603 if (is_object($data)) {
1604 $data = json_decode(json_encode($data), true);
1605 }
1606 return $data;
1607 }
1608
1609 /**
1610 * Convert Object to Array
1611 *
1612 * @return array
1613 *
1614 * @since 1.0
1615 */
1616 public static function get_free_retriever_data()
1617 {
1618 if (Util::sbr_is_pro()) {
1619 $retriever = new \SmashBalloon\Reviews\Pro\Utils\FreeRetriever();
1620 } else {
1621 $retriever = new \SmashBalloon\Reviews\Common\Utils\FreeRetriever();
1622 }
1623 return $retriever->get_settings();
1624 }
1625
1626 /**
1627 * Get Feeds Settings
1628 *
1629 * @since 1.0
1630 *
1631 * @return string
1632 */
1633 public static function get_settings_page_errors()
1634 {
1635 $output = '## ERROR LOGS: ## </br></br>';
1636 $errors = SBR_Error_Handler::get_errors();
1637 $errors = array_reverse($errors);
1638 foreach ($errors as $error) {
1639 $output .= json_encode($error);
1640 $output .= '</br></br>';
1641 }
1642 $output .= '</br>';
1643
1644 return $output;
1645 }
1646
1647 /**
1648 * Get Local Images/Avatar
1649 * Upload folder name
1650 *
1651 * @since 2.0
1652 *
1653 * @return string
1654 */
1655 public static function get_upload_folder_name()
1656 {
1657 $upload = wp_upload_dir();
1658 $folder = trailingslashit($upload['basedir']) . trailingslashit('sbr-feed-images');
1659 return $folder;
1660 }
1661
1662 public static function should_store_local_images()
1663 {
1664 $settings = get_option('sbr_settings', sbr_plugin_settings_defaults());
1665 if (! is_array($settings)) {
1666 $settings = sbr_plugin_settings_defaults();
1667 }
1668 return !empty($settings['optimize_images']) ? $settings['optimize_images'] : true;
1669 }
1670
1671 }
1672