PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.10.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.10.0
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 / sbr-functions.php
reviews-feed / class Last commit date
Common 3 weeks ago sbr-functions.php 3 weeks ago
sbr-functions.php
969 lines
1 <?php
2
3 use SmashBalloon\Reviews\Common\Admin\Blocks\SB_Reviews_Blocks;
4 use SmashBalloon\Reviews\Common\Util;
5
6 if (!defined('ABSPATH')) {
7 exit; // Exit if accessed directly
8 }
9
10 function sbr_json_encode($thing)
11 {
12 if (function_exists('wp_json_encode')) {
13 return wp_json_encode($thing);
14 } else {
15 return json_encode($thing);
16 }
17 }
18
19
20 /**
21 * Reviews Currect User Capability Check
22 *
23 * @since 1.0
24 */
25 function sbr_current_user_can($cap)
26 {
27 if ($cap === 'manage_reviews_feed_options') {
28 $cap = current_user_can('manage_reviews_feed_options') ? 'manage_reviews_feed_options' : 'manage_options';
29 }
30 $cap = apply_filters('sbr_settings_pages_capability', $cap);
31
32 return current_user_can($cap);
33 }
34
35
36 /**
37 * Get the settings in the database with defaults
38 *
39 * @return array
40 */
41 function sbr_get_database_settings()
42 {
43 global $sbr_settings;
44
45 $defaults = sbr_settings_defaults();
46
47 if ($sbr_settings === null) {
48 $sbr_settings = get_option('sbr_settings', []);
49 }
50
51 // Defensive: `sbr_settings` can arrive as a non-array (raw SQL edits,
52 // broken backup/restore, migration tooling that mangled serialization).
53 // Without this guard `array_merge()` fatals and takes the whole admin
54 // down. Normalizing to an array lets SMASH-1281's migration-recovery
55 // flow re-register + repopulate on the next page load.
56 if (!is_array($sbr_settings)) {
57 $sbr_settings = [];
58 }
59
60 return array_merge($defaults, $sbr_settings);
61 }
62
63 /**
64 * Get the settings default settins
65 *
66 * @return array
67 */
68 function sbr_settings_defaults()
69 {
70 return [
71 //Template
72 'feedTemplate' => 'default',
73 //Sources
74 'sources' => [],
75 //Layout Settings
76 'layout' => 'list',
77 'verticalSpacing' => 20,
78 'horizontalSpacing' => 10,
79 'contentAlignment' => 'left',
80 'contentLength' => 320,
81 'numPostDesktop' => 10,
82 'numPostTablet' => 8,
83 'numPostMobile' => 6,
84 'gridDesktopColumns' => 3,
85 'gridTabletColumns' => 2,
86 'gridMobileColumns' => 1,
87 'masonryDesktopColumns' => 3,
88 'masonryTabletColumns' => 2,
89 'masonryMobileColumns' => 1,
90 'carouselDesktopColumns' => 3,
91 'carouselTabletColumns' => 2,
92 'carouselMobileColumns' => 1,
93 'carouselDesktopRows' => 1,
94 'carouselTabletRows' => 1,
95 'carouselMobileRows' => 1,
96 'carouselLoopType' => 'infinity',
97 'carouselIntervalTime' => 5000,
98 'carouselShowArrows' => false,
99 'carouselShowPagination' => true,
100 'carouselEnableAutoplay' => true,
101
102 //Header
103 'showHeader' => true,
104 'headerContent' => ['heading', 'button', 'averagereview'],
105 'headerPadding' => [],
106 'headerMargin' => ['bottom' => 20],
107 //Heading
108 'headerHeadingContent' => 'Reviews',
109 'headingFont' => [
110 'weight' => 700,
111 'size' => 36,
112 'height' => '100%'
113 ],
114 'headingColor' => '#141B38',
115 'headerHeadingPadding' => [],
116 'headerHeadingMargin' => ['bottom' => 10],
117 //Button
118 'headerButtonLinkTo' => 'google',
119 'headerButtonIcon' => '',
120 'headerButtonExternalLink' => '',
121 'headerButtonFont' => [
122 'weight' => 600,
123 'size' => 14,
124 'height' => '22px'
125 ],
126 'headerButtonColor' => '#ffffff',
127 'headerButtonBg' => '#ED4944',
128 'headerButtonHoverColor' => '#ffffff',
129 'headerButtonHoverBg' => '#CC3F3A',
130 'headerButtonPadding' => [
131 'top' => 8,
132 'right' => 20,
133 'bottom' => 8,
134 'left' => 12,
135 ],
136 'headerButtonMargin' => [],
137 //AverageReview
138 'headerAvReviewFont' => [
139 'weight' => 600,
140 'size' => 20,
141 'height' => '1.5em'
142 ],
143 'headerAvSubtextReviewFont' => [
144 'weight' => 400,
145 'size' => 12,
146 'height' => '1.5em'
147 ],
148 'headerAvReviewIconColor' => '#ED4944',
149 'headerAvReviewColor' => '#141B38',
150 'headerAvReviewSubtextColor' => '#434960',
151 'headerAvReviewMargin' => '',
152 'headerAvReviewPadding' => '',
153
154 //Post Style
155 'postStyle' => 'regular',
156 'boxedBackgroundColor' => '#ffffff',
157 'boxedBoxShadow' => [],
158 'boxedBorderRadius' => [],
159 'postStroke' => [],
160 'postPadding' => [
161 'bottom' => 20
162 ],
163
164 'postElements' => ['author', 'rating', 'text', 'media'],
165 'ratingIconSize' => 'small',
166 'ratingIconColor' => '#ED4944',
167 'ratingIconPadding' => [],
168 'ratingIconMargin' => [
169 'top' => 15,
170 'bottom' => 15,
171 ],
172 'paragraphFont' => [
173 'weight' => 400,
174 'size' => 16,
175 'height' => '1.5em'
176 ],
177 'paragraphColor' => '#434960',
178 'paragraphPadding' => [],
179 'paragraphMargin' => [],
180
181 'authorContent' => ['name', 'image', 'date'],
182 'authorPadding' => [],
183 'authorMargin' => [],
184
185 'authorNameFont' => [
186 'weight' => 600,
187 'size' => 14,
188 'height' => '1.5em'
189 ],
190 'authorNameColor' => '#141B38',
191 'authorNamePadding' => [],
192 'authorNameMargin' => [],
193
194 'dateFont' => [
195 'weight' => 400,
196 'size' => 13,
197 'height' => '1.5em'
198 ],
199 'dateColor' => '#434960',
200
201 'dateFormat' => '1',
202 'dateCustomFormat' => '',
203 'dateBeforeText' => '',
204 'dateAfterText' => '',
205 'datePadding' => [],
206 'dateMargin' => [],
207 'authorImageBorderRadius' => 50,
208 'authorImageMargin' => [
209 'right' => 10
210 ],
211
212 'showLoadButton' => true,
213 'loadButtonText' => 'Load More',
214 'loadButtonFont' => [
215 'weight' => 600,
216 'size' => 16,
217 'height' => '1em'
218 ],
219 'loadButtonColor' => '#141B38',
220 'loadButtonHoverColor' => '#ffffff',
221 'loadButtonBg' => '#E6E6EB',
222 'loadButtonHoverBg' => '#FE544F',
223
224 'loadButtonPadding' => [
225 'top' => 15,
226 'bottom' => 15
227 ],
228 'loadButtonMargin' => [
229 'top' => 20
230 ],
231
232 //Filters
233 'includedStarFilters' => [],
234 'includeWords' => '',
235 'excludeWords' => '',
236 'filterByImage' => false,
237 'filterByVideos' => true,
238
239 //Sort
240 'sortByDateEnabled' => true,
241 'sortByDate' => 'latest',
242
243 'sortByRatingEnabled' => false,
244 'sortByRating' => '',
245
246 'sortRandomEnabled' => false,
247
248 //ColorScheme
249 'colorScheme' => 'inherit',
250
251
252 //Moderation Mode
253 'moderationEnabled' => false,
254 'moderationType' => 'allow',
255 'moderationAllowList' => [],
256 'moderationBlockList' => [],
257
258 //Translation
259 'localization' => 'default',
260 'trustpilotLanguage' => 'all',
261
262 //Filter By Length
263 'filterCharCountMin' => 0,
264 'filterCharCountMax' => '',
265
266 //Carousel Breakpoints
267 'carouselBreakpointDesktop' => 850,
268 'carouselBreakpointTablet' => 520
269 ];
270 }
271
272
273 function sbr_plugin_settings_defaults()
274 {
275 return [
276 'localization' => '',
277 'optimize_images' => true,
278 'usagetracking' => true,
279 'enqueue_js_in_header' => false,
280 'admin_error_notices' => true,
281 'feed_issue_reports' => true,
282 // SMASH-1756 — emit schema.org rich-snippet markup for feeds (global, default on).
283 'enableSchema' => true,
284 'translations' => [
285 'second' => __('second', 'reviews-feed'),
286 'seconds' => __('seconds', 'reviews-feed'),
287 'minute' => __('minute', 'reviews-feed'),
288 'minutes' => __('minutes', 'reviews-feed'),
289 'hour' => __('hour', 'reviews-feed'),
290 'hours' => __('hours', 'reviews-feed'),
291 'day' => __('day', 'reviews-feed'),
292 'days' => __('days', 'reviews-feed'),
293 'week' => __('week', 'reviews-feed'),
294 'weeks' => __('weeks', 'reviews-feed'),
295 'month' => __('month', 'reviews-feed'),
296 'months' => __('months', 'reviews-feed'),
297 'year' => __('year', 'reviews-feed'),
298 'years' => __('year', 'reviews-feed'),
299 'ago' => __('ago', 'reviews-feed'),
300 'writeReview' => __('Write a Review', 'reviews-feed'),
301 'reviewsHeader' => __('Over %s Reviews', 'reviews-feed'),
302 ]
303 ];
304 }
305 function sbr_activate($network_wide)
306 {
307 global $wp_roles;
308 $wp_roles->add_cap('administrator', 'manage_reviews_feed_options');
309 }
310
311 register_activation_hook(__FILE__, 'sby_activate');
312
313
314 function sbr_get_feed_template_part($part, $settings = array())
315 {
316 $file = '';
317
318 /**
319 * Whether or not to search for custom templates in theme folder
320 *
321 * @param boolean Setting from DB or shortcode to use custom templates
322 *
323 * @since 1.0
324 */
325 $settings_custom_templates = ! empty($settings['customtemplates']) && $settings['customtemplates'];
326 $using_custom_templates_in_theme = apply_filters('sbr_use_theme_templates', $settings_custom_templates);
327 $generic_path = trailingslashit(SBR_PLUGIN_DIR) . 'templates/frontend/';
328
329 //For Templates that are different Free Or Pro
330 $special_path = $generic_path . ( Util::sbr_is_pro() ? 'pro' : 'lite' ) . '/';
331
332 if ($using_custom_templates_in_theme) {
333 $custom_header_template = locate_template('sbr/header.php', false, false);
334 $custom_item_template = locate_template('sbr/item.php', false, false);
335 $custom_footer_template = locate_template('sbr/footer.php', false, false);
336 $custom_feed_template = locate_template('sbr/feed.php', false, false);
337 } else {
338 $custom_header_template = false;
339 $custom_item_template = false;
340 $custom_footer_template = false;
341 $custom_feed_template = false;
342 }
343
344 if ($part === 'header') {
345 if ($custom_header_template) {
346 $file = $custom_header_template;
347 } else {
348 #$file = $generic_path . 'header.php';
349 $file = $special_path . 'header.php';
350 }
351 } elseif ($part === 'item') {
352 if ($custom_item_template) {
353 $file = $custom_item_template;
354 } else {
355 $file = $generic_path . 'item.php';
356 }
357 } elseif ($part === 'footer') {
358 if ($custom_footer_template) {
359 $file = $custom_footer_template;
360 } else {
361 #$file = $generic_path . 'footer.php';
362 $file = $special_path . 'footer.php';
363 }
364 } elseif ($part === 'feed') {
365 if ($custom_feed_template) {
366 $file = $custom_feed_template;
367 } else {
368 #$file = $generic_path . 'feed.php';
369 $file = $special_path . 'feed.php';
370 }
371 } elseif ($part === 'post-elements/author') {
372 if ($custom_feed_template) {
373 $file = $custom_feed_template;
374 } else {
375 #$file = $generic_path . 'post-elements/author.php';
376 $file = $special_path . 'post-elements/author.php';
377 }
378 } elseif ($part === 'post-elements/media') {
379 if ($custom_feed_template) {
380 $file = $custom_feed_template;
381 } else {
382 #$file = $generic_path . 'post-elements/media.php';
383 $file = $special_path . 'post-elements/media.php';
384 }
385 } elseif ($part === 'post-elements/rating') {
386 if ($custom_feed_template) {
387 $file = $custom_feed_template;
388 } else {
389 $file = $generic_path . 'post-elements/rating.php';
390 }
391 } elseif ($part === 'post-elements/text') {
392 if ($custom_feed_template) {
393 $file = $custom_feed_template;
394 } else {
395 $file = $generic_path . 'post-elements/text.php';
396 }
397 }
398
399 return $file;
400 }
401
402 function sbr_container_id($feed_id)
403 {
404 return 'sb-reviews-container-' . $feed_id;
405 }
406
407 function sbr_scripts_enqueue($enqueue = false)
408 {
409 //Register the script to make it available
410 $assets_url = trailingslashit(SBR_PLUGIN_URL);
411 $settings = get_option('sbr_settings', []);
412 if (!is_array($settings)) {
413 $settings = [];
414 }
415 $min = !empty($_GET['sb_debug']) ? '' : '.min';
416
417 wp_enqueue_style(
418 'sbr_styles',
419 $assets_url . 'assets/css/sbr-styles' . $min . '.css',
420 [],
421 SBRVER
422 );
423
424 if (!empty($settings['enqueue_js_in_header'])) {
425 wp_enqueue_script(
426 'sbr_scripts',
427 $assets_url . 'assets/js/sbr-feed' . $min . '.js',
428 ['jquery'],
429 SBRVER,
430 false
431 );
432 } else {
433 wp_register_script(
434 'sbr_scripts',
435 $assets_url . 'assets/js/sbr-feed' . $min . '.js',
436 ['jquery'],
437 SBRVER,
438 true
439 );
440 }
441
442 $data = array(
443 'adminAjaxUrl' => admin_url('admin-ajax.php'),
444 // Translatable strings for JS-injected accessible names and SR announcements (WCAG 3.1.2).
445 'a11y' => array(
446 /* translators: %s: number of reviews just loaded. Announced to screen readers after Load More. */
447 'reviewLoaded' => __('%s review loaded.', 'reviews-feed'),
448 /* translators: %s: number of reviews just loaded. Announced to screen readers after Load More. */
449 'reviewsLoaded' => __('%s reviews loaded.', 'reviews-feed'),
450 'allReviewsShown' => __('All reviews shown.', 'reviews-feed'),
451 'photoViewer' => __('Review photo viewer', 'reviews-feed'),
452 'previousPhoto' => __('Previous photo', 'reviews-feed'),
453 'nextPhoto' => __('Next photo', 'reviews-feed'),
454 'closePhotoViewer' => __('Close photo viewer', 'reviews-feed'),
455 'close' => __('Close', 'reviews-feed'),
456 'previous' => __('Previous', 'reviews-feed'),
457 'next' => __('Next', 'reviews-feed'),
458 'reviewerPhoto' => __('Reviewer photo', 'reviews-feed'),
459 ),
460 );
461 //Pass option to JS file
462 wp_localize_script('sbr_scripts', 'sbrOptions', $data);
463
464 if ($enqueue || SB_Reviews_Blocks::is_gb_editor()) {
465 wp_enqueue_style('sbr_styles');
466 wp_enqueue_script('sbr_scripts');
467 }
468 }
469 add_action('wp_enqueue_scripts', 'sbr_scripts_enqueue', 2);
470
471 function sbr_esc_html_with_br($text)
472 {
473 return str_replace(array( '&lt;br /&gt;', '&lt;br&gt;' ), '<br>', esc_html(nl2br($text)));
474 }
475
476 /**
477 * Neutralize WordPress shortcodes in third-party review content before output.
478 *
479 * Review data imported from connected sources (Google, Yelp, Booking.com, EDD,
480 * etc.) is rendered inside the dynamic `sbr/sbr-feed-block`. WordPress runs
481 * `do_blocks()` on `the_content` at priority 9 and `do_shortcode()` at priority
482 * 11, so any shortcode left in the rendered block markup is expanded
483 * server-side — including a shortcode an unauthenticated visitor planted in a
484 * public review (e.g. a reviewer name or review body of `[gallery ids=1]`).
485 * The escaping helpers (`esc_html()`, `sbr_kses_review_text()`) deliberately leave the
486 * `[` and `]` characters untouched, so they do not stop this on their own.
487 *
488 * Encoding the square brackets to HEX HTML entities (`&#x5B;` / `&#x5D;`) keeps
489 * the literal text visible to the visitor (the browser renders them as `[` / `]`)
490 * while ensuring `do_shortcode()` can never match them. Apply this as the
491 * OUTERMOST wrapper around already-escaped output: `esc_html()` / `sbr_kses_review_text()`
492 * run first on the raw text (they leave `[` and `]` alone), then this encodes the
493 * brackets last.
494 *
495 * HEX, not decimal, is mandatory here: WordPress core's `do_shortcode()` ends by
496 * calling `unescape_invalid_shortcodes()`, which runs
497 * `str_replace( array( '&#91;', '&#93;' ), array( '[', ']' ), $content )` over the
498 * processed content. That reverses the DECIMAL entities `&#91;` / `&#93;` straight
499 * back to raw `[` / `]` — re-arming the very shortcode we just neutralized (the
500 * feed renders through `do_shortcode` via the `[reviews-feed]` shortcode and again
501 * through the block + `the_content` chain, so this fires in practice). It does NOT
502 * touch the hex forms `&#x5B;` / `&#x5D;`, so those survive intact. Using decimal
503 * here is self-defeating; see SMASH-1607 follow-up (CVE-2026-10724 regression).
504 *
505 * @since 2.6.5
506 *
507 * @see https://awesomemotive.atlassian.net/browse/SMASH-1607 (CVE-2026-10724)
508 *
509 * Non-string (e.g. null) or empty input is returned unchanged, so the type is
510 * intentionally permissive — callers pass already-escaped output, but the guard
511 * keeps a stray null/empty safe rather than coercing it.
512 *
513 * @param string|null $text Already-escaped output that may contain shortcode brackets.
514 * @return string|null The text with `[` and `]` encoded to hex HTML entities (`&#x5B;` / `&#x5D;`); the input unchanged if it isn't a non-empty string.
515 */
516 function sbr_neutralize_shortcodes($text)
517 {
518 if (! is_string($text) || $text === '') {
519 return $text;
520 }
521
522 // Hex entities (not decimal): WordPress core's unescape_invalid_shortcodes()
523 // str_replaces decimal &#91;/&#93; back to [/] inside do_shortcode(), which would
524 // re-arm the shortcode. Hex forms are not reversed. See docblock + SMASH-1607.
525 return str_replace(array( '[', ']' ), array( '&#x5B;', '&#x5D;' ), $text);
526 }
527
528 /**
529 * Allowlist for rendering a review body. Use this instead of wp_kses_post().
530 *
531 * SMASH-1795 — wp_kses_post() is the *post editor* allowlist and keeps `<img>` with
532 * its class/src/alt, which the feed script then re-parsed out of the alt. This permits
533 * WordPress's comment-formatting set instead: emphasis, lists, quotes, headings and
534 * `a[href|title|rel]`. Links are allowed because Woo/EDD bodies contain them and
535 * wp_kses() drops a disallowed protocol from the href. `img` never is, nor any
536 * attribute the front end reads back and re-parses.
537 *
538 * Runs on the READ path, at every review-text sink, so it also covers bodies already
539 * stored and writers that bypass the write-side filter — Woo and EDD pass
540 * `comment_content` straight into `$review['text']`. `nl2br()` output survives.
541 *
542 * @param string|null $text Raw review body.
543 * @return string Sanitised body, safe to echo.
544 */
545 function sbr_kses_review_text($text)
546 {
547 if (! is_string($text) || $text === '') {
548 return '';
549 }
550
551 // Resolve an emoji image to its alt BEFORE the allowlist drops the tag, so a 😀
552 // is shown rather than silently deleted. Server-side twin of stripEmojihtml()
553 // (assets/js/sbr-feed.js), resolved the same way — decode once, then escape — so
554 // a payload hidden in the alt lands as inert text here too.
555 // Never let a PCRE failure blank the review. preg_replace_callback() returns NULL
556 // on any engine error (backtrack/recursion limit, JIT stack) and `(string) null`
557 // is '' — which would silently empty the body instead of degrading to unresolved
558 // emoji markup. Same failure shape as the read-more blanking bug (ae11c55): the
559 // safe fallback is the text we already had.
560 //
561 // Measured, so the next reader doesn't have to re-derive it: the pattern is NOT
562 // quadratic on a `>`-less run. Six adversarial shapes — repeated `class=`,
563 // repeated `emoji`, unterminated quotes, stacked `<img` prefixes — at 4 KB to
564 // 36 KB all return in under 1.4ms with preg_last_error() == PREG_NO_ERROR on
565 // PHP 8.2 (pcre.backtrack_limit 1000000). The character classes exclude `>`, so
566 // the required literals gate progression and PCRE fails fast. Invalid UTF-8 is
567 // not a NULL route either: there is no /u modifier, so the match is byte-wise.
568 // The guard is here because the cast was unsafe in principle, not because a
569 // reachable payload was found.
570 $emoji_resolved = preg_replace_callback(
571 '#<img\b[^>]*\bclass\s*=\s*["\']?[^"\'>]*\bemoji\b[^"\'>]*["\']?[^>]*>#i',
572 static function ($match) {
573 if (preg_match('#\balt\s*=\s*("([^"]*)"|\'([^\']*)\'|([^\s>]+))#i', $match[0], $alt) !== 1) {
574 return '';
575 }
576 $value = $alt[2] ?? '';
577 if ($value === '') {
578 $value = $alt[3] ?? '';
579 }
580 if ($value === '') {
581 $value = $alt[4] ?? '';
582 }
583 return esc_html(html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
584 },
585 $text
586 );
587 if (is_string($emoji_resolved)) {
588 $text = $emoji_resolved;
589 }
590
591 /**
592 * Filter the tags allowed in a rendered review body.
593 *
594 * Intentionally narrow. Widening this re-opens SMASH-1795 if a tag that can
595 * carry an attribute the front end later reads back is added.
596 *
597 * MUST return an array. A non-array return is ignored — see the guard below.
598 *
599 * @param array<string,array<string,array<mixed>>> $tags Allowed tags in wp_kses() format.
600 */
601 $default = array(
602 'br' => array(),
603 'em' => array(),
604 'strong' => array(),
605 'b' => array(),
606 'i' => array(),
607 'p' => array(),
608 'span' => array(),
609 // Woo/EDD bodies are raw comment_content, so they legitimately carry the
610 // WP-comment markup below; a narrower list drops it from stored reviews.
611 // No `target`: permitting it without forcing rel="noopener" hands the opened
612 // page a window.opener handle. WP's comment allowlist omits it too.
613 'a' => array('href' => array(), 'title' => array(), 'rel' => array()),
614 'blockquote' => array('cite' => array()),
615 'q' => array('cite' => array()),
616 'cite' => array(),
617 'code' => array(),
618 'pre' => array(),
619 'del' => array(),
620 'ins' => array(),
621 'ul' => array(),
622 'ol' => array(),
623 'li' => array(),
624 's' => array(),
625 'strike' => array(),
626 'u' => array(),
627 'sub' => array(),
628 'sup' => array(),
629 'hr' => array(),
630 'abbr' => array('title' => array()),
631 'acronym' => array('title' => array()),
632 'h1' => array(),
633 'h2' => array(),
634 'h3' => array(),
635 'h4' => array(),
636 'h5' => array(),
637 'h6' => array(),
638 );
639
640 $allowed = apply_filters('sbr_allowed_review_text_tags', $default);
641
642 // wp_kses() reads a STRING second argument as a CONTEXT NAME, so a filter
643 // returning 'post' resolves $allowedposttags — img included — and re-opens this
644 // exact chain. Non-arrays fall back to the default rather than being trusted.
645 if (! is_array($allowed)) {
646 $allowed = $default;
647 }
648
649 return wp_kses($text, $allowed);
650 }
651
652
653
654
655 function sbr_get_fb_connection_urls($is_settings = false)
656 {
657 $urls = array();
658 $admin_url_state = $is_settings ?
659 admin_url('admin.php?page=sbr-settings') :
660 admin_url('admin.php?page=sbr');
661 $sb_admin_email = get_option('admin_email');
662 $nonce = wp_create_nonce('cff_con');
663 $sw_flag = !empty($_GET['sw-feed']) ? true : false;
664
665 // If the admin_url isn't returned correctly then use a fallback.
666 if (
667 $admin_url_state === '/wp-admin/admin.php?page=sbr'
668 || $admin_url_state === '/wp-admin/admin.php?page=sbr&tab=configuration'
669 ) {
670 $admin_url_state = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
671 }
672
673 $urls['page'] = [
674 'connect' => SBR_FB_CONNECT_URL,
675 'wordpress_user' => $sb_admin_email,
676 'v' => 'pro',
677 'vn' => SBRVER,
678 'cff_con' => $nonce,
679 'sw_feed' => $sw_flag
680 ];
681
682 $urls['stateURL'] = $admin_url_state;
683 return $urls;
684 }
685
686 function check_license_valid()
687 {
688 $sbr_settings = get_option('sbr_settings', []);
689 return isset($sbr_settings['license_key'])
690 && !empty($sbr_settings['license_key'])
691 && isset($sbr_settings['license_status'])
692 && !empty($sbr_settings['license_status'])
693 && $sbr_settings['license_status'] !== 'invalid';
694 }
695
696 function sbr_plugin_action_links($links)
697 {
698 $settings_link = check_license_valid() ? admin_url('admin.php?page=sbr-settings') : admin_url('admin.php?page=sbr');
699 $support_link = check_license_valid() ? admin_url('admin.php?page=sbr-support') : admin_url('admin.php?page=sbr');
700 $links = array_merge(
701 array(
702 '<a href="' . esc_url($settings_link) . '">' . __('Settings', 'reviews-feed') . '</a>'
703 ),
704 $links
705 );
706
707 if (!Util::sbr_is_pro()) {
708 $links = array_merge(
709 array(
710 '<a href="https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=plugins-page&utm_medium=upgrade-link&utm_content=UpgradeToPro" target="_blank" style="font-weight:bold; color: #50a56d;">' . __('Upgrade to Pro', 'reviews-feed') . '</a>'
711 ),
712 $links
713 );
714 } else {
715 $links = array_merge(
716 array(
717 '<a href="' . esc_url($support_link) . '">' . __('Support', 'reviews-feed') . '</a>'
718 ),
719 $links
720 );
721 }
722
723 return $links;
724 }
725 add_action('plugin_action_links_' . SBR_PLUGIN_BASENAME, 'sbr_plugin_action_links');
726
727
728 add_action('current_screen', 'sbr_check_current_screen');
729
730 function sbr_check_current_screen()
731 {
732 if (Util::currentPageIs('sbr')) {
733 add_action('admin_enqueue_scripts', 'dequeue_smash_plugins_style');
734 }
735 }
736
737 function dequeue_smash_plugins_style()
738 {
739 wp_dequeue_style('cff_custom_wp_admin_css');
740 wp_deregister_style('cff_custom_wp_admin_css');
741
742 wp_dequeue_style('feed-global-style');
743 wp_deregister_style('feed-global-style');
744
745 wp_dequeue_style('sb_instagram_admin_css');
746 wp_deregister_style('sb_instagram_admin_css');
747
748 wp_dequeue_style('ctf_admin_styles');
749 wp_deregister_style('ctf_admin_styles');
750 }
751
752 function sbr_custom_menu()
753 {
754 if (Util::sbr_is_pro() === false) {
755 $cap = current_user_can('manage_reviews_feed_options') ? 'manage_reviews_feed_options' : 'manage_options';
756 $cap = apply_filters('sbr_settings_pages_capability', $cap);
757 add_submenu_page(
758 'sbr',
759 __('Upgrade to Pro', 'reviews-feed'),
760 __('<div class="sb-pro-upgradelink-bg"></div><strong class="sb-pro-upgradelink">Upgrade to Pro</strong>', 'reviews-feed'),
761 $cap,
762 'https://smashballoon.com/reviews-feed/reviews-lite-upgrade/?utm_campaign=reviews-free&utm_source=menu-link&utm_medium=upgrade-link&utm_content=UpgradeToPro',
763 ''
764 );
765 }
766 }
767
768 add_action('admin_menu', 'sbr_custom_menu', 40);
769
770
771 function sbr_text_domain()
772 {
773 load_plugin_textdomain('reviews-feed', false, dirname(SBR_PLUGIN_BASENAME) . '/languages');
774 }
775 add_action('init', 'sbr_text_domain');
776
777 function sbr_get_current_time()
778 {
779 $current_time = time();
780
781 // where to do tests
782 //$current_time = strtotime( 'November 25, 2020' );
783
784 return $current_time;
785 }
786
787
788 function sbr_recursive_parse_args($args, $defaults)
789 {
790 $new_args = (array) $defaults;
791
792 foreach ($args as $key => $value) {
793 if (is_array($value) && isset($new_args[ $key ])) {
794 $new_args[ $key ] = sbr_recursive_parse_args($value, $new_args[ $key ]);
795 } else {
796 $new_args[ $key ] = $value;
797 }
798 }
799 return $new_args;
800 }
801
802 function sbr_doing_openssl()
803 {
804 return extension_loaded('openssl');
805 }
806
807 function sbr_encrypt_decrypt($action, $string)
808 {
809 $output = false;
810
811 $encrypt_method = "AES-256-CBC";
812 $secret_key = 'SMA$H.BA[[OON#23121';
813 $secret_iv = '1231394873342102221';
814
815 // hash
816 $key = hash('sha256', $secret_key);
817
818 // iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
819 $iv = substr(hash('sha256', $secret_iv), 0, 16);
820
821 if ($action === 'encrypt') {
822 $output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
823 $output = base64_encode($output);
824 } elseif ($action === 'decrypt') {
825 $output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
826 }
827
828 return $output;
829 }
830
831
832 /**
833 * Returns a list of providers that needs
834 * the bulk history update
835 *
836 * @return array
837 *
838 * @since 1.5
839 */
840 function sbr_get_bulk_providers()
841 {
842 return [
843 'google',
844 'yelp'
845 ];
846 }
847
848 /**
849 * Returns a list of providers that
850 * have media
851 *
852 * @return array
853 *
854 * @since 1.5
855 */
856 function sbr_get_media_providers()
857 {
858 return [
859 'yelp',
860 'tripadvisor',
861 'google'
862 ];
863 }
864
865 /**
866 * Returns a list of providers that doesn't
867 * have media
868 *
869 * @return array
870 *
871 * @since 1.5
872 */
873 function sbr_get_no_media_providers()
874 {
875 return [
876 'facebook',
877 'woocommerce',
878 'edd',
879 'airbnb',
880 'booking',
881 'aliexpress'
882 ];
883 }
884
885 /**
886 * Returns a list of providers that have
887 * translations / languages Possibility
888 *
889 * @return array
890 *
891 * @since 1.5
892 */
893 function sbr_get_lang_providers()
894 {
895 return [
896 'google'
897 ];
898 }
899
900 /**
901 * Booking.com score band for a single review, on Booking's native 0-10 scale.
902 *
903 * Booking does NOT send a per-review qualifier: the only word its API returns is
904 * `review_score_word`, and that belongs to the PROPERTY (sb-relay
905 * RapidBookingRemoteSourcesRepository:492 — the SOURCE repository, not the reviews one).
906 * Verified on booking.com itself: the property block reads "Scored 9.5 / Exceptional",
907 * while an individual review card reads "Scored 8.0" followed straight by the review text,
908 * with no word. The per-review `title` is the guest's own headline, not a band — at rating 4
909 * our cached rows carry "Very good", "Fabulous", "Wonderful", "Good" and "Fantasico" all at
910 * once, so it cannot be a label.
911 *
912 * So the band is derived from the reviewer's own score. Provenance of each threshold,
913 * because it matters if these ever need defending:
914 * 9.5+ Exceptional — confirmed twice: Booking's API sent it for source 12166067 at 9.5,
915 * and booking.com renders "Rated exceptional / Exceptional" there.
916 * 9.0+ Superb — confirmed: Booking's API sent it for source 280149 at 9.4. Note
917 * third-party write-ups claim "Excellent" for this band; the API
918 * disagrees, and the API wins.
919 * 8.0+ Very good — Booking's published ladder. Their capitalisation, not "Very Good".
920 * 7.0+ Good — Booking's published ladder.
921 * 6.0+ Pleasant — Booking's published ladder.
922 * below — no word; Booking shows only the number.
923 *
924 * @param float $score Score on the 0-10 scale.
925 * @return string Band word, or '' when the score is below the lowest named band.
926 */
927 function sbr_booking_score_word($score)
928 {
929 $score = (float) $score;
930
931 if ($score >= 9.5) {
932 return __('Exceptional', 'reviews-feed');
933 }
934 if ($score >= 9) {
935 return __('Superb', 'reviews-feed');
936 }
937 if ($score >= 8) {
938 return __('Very good', 'reviews-feed');
939 }
940 if ($score >= 7) {
941 return __('Good', 'reviews-feed');
942 }
943 if ($score >= 6) {
944 return __('Pleasant', 'reviews-feed');
945 }
946
947 return '';
948 }
949
950 /**
951 * A single review's Booking score on the native 0-10 scale, from the reviewer's own rating.
952 *
953 * `$post['rating']` is stored 0-5 like every other provider. Booking's reviews API actually
954 * sends `average_score` on a 0-4 scale and the relay converts it with
955 * `round($value * 1.25, 1)` (sb-relay RapidRemoteBookingReviewsRepository::convertRating),
956 * so doubling back inherits up to ±0.125 of that rounding. Exactness needs the relay to
957 * forward the raw score. Verified against booking.com: our 4 -> 8.0 and 4.5 -> 9.0 match the
958 * "Scored 8.0" / "Scored 9.0" cards on the live page for source 12166067.
959 *
960 * @param array $post Normalised review.
961 * @return float Score on the 0-10 scale; 0.0 when there is no usable rating.
962 */
963 function sbr_booking_review_score($post)
964 {
965 $rating = isset($post['rating']) ? (float) $post['rating'] : 0.0;
966
967 return $rating > 0 ? round($rating * 2, 1) : 0.0;
968 }
969