PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.0
ShopBuilder – WooCommerce Builder For Elementor v3.2.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / Helpers / Fns.php

Fns.php in ShopBuilder – WooCommerce Builder For Elementor 3.2.0, at app/Helpers/Fns.php

4,048 lines 116.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Fns Helpers class
4 *
5 * @package RadiusTheme\SB
6 */
7
8 namespace RadiusTheme\SB\Helpers;
9
10 // Do not allow directly accessing this file.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit( 'This script cannot be accessed directly.' );
13 }
14
15
16 use CodesVault\Howdyqb\DB;
17 use DateTime;
18 use WP_Styles;
19 use WC_Product;
20 use Elementor\Plugin;
21 use WC_Product_Query;
22 use Elementor\Icons_Manager;
23 use RadiusTheme\SB\Models\ReSizer;
24 use RadiusTheme\SB\Models\Settings;
25 use RadiusTheme\SB\Models\DataModel;
26 use RadiusTheme\SB\Models\ModuleList;
27 use RadiusTheme\SB\Models\GeneralList;
28 use RadiusTheme\SB\Models\ElementList;
29 use RadiusTheme\SB\Controllers\AssetRegistry;
30
31 /**
32 * Fns class
33 */
34 class Fns {
35
36 /**
37 * @var array
38 */
39 private static $cache = [];
40
41 /**
42 * Verify nonce.
43 *
44 * @return bool
45 */
46 public static function verify_nonce() {
47 $nonce = isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ rtsb()->nonceId ] ) ) : null;
48 $nonceText = rtsb()->nonceText;
49 if ( wp_verify_nonce( $nonce, $nonceText ) ) {
50 return true;
51 }
52
53 return false;
54 }
55
56 /**
57 * Get nonce.
58 *
59 * @return string|null
60 */
61 public static function enable_loader() {
62 return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
63 }
64 /**
65 * Get nonce.
66 *
67 * @return string|null
68 */
69 public static function content_invisible() {
70 $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
71 if ( $wp_rocket_compatibility ) {
72 return true;
73 }
74 if ( defined( 'WP_ROCKET_VERSION' ) ) {
75 return false;
76 }
77 return true;
78 }
79 /**
80 * Get nonce.
81 *
82 * @return string|null
83 */
84 public static function get_nonce() {
85 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
86 return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
87 }
88
89 /**
90 * Set Session
91 *
92 * @param string $name Name.
93 * @param mixed $value Value.
94 */
95 public static function setSession( $name, $value ) {
96 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
97 session_start();
98 $_SESSION[ $name ] = $value;
99 } else {
100 $_SESSION[ $name ] = $value;
101 }
102 }
103
104 /**
105 * Get Cookie or Session
106 *
107 * @param string $name Name.
108 *
109 * @return bool
110 */
111 public static function getSession( $name ) {
112 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
113 session_start();
114 }
115 return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
116 }
117 /**
118 * Remove Session Variable
119 *
120 * @param string $name The name of the session variable to remove.
121 */
122 public static function removeSession( $name ) {
123 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
124 session_start();
125 unset( $_SESSION[ $name ] );
126 } else {
127 unset( $_SESSION[ $name ] );
128 }
129 }
130 /**
131 * Gets the current timestamp in WordPress timezone.
132 *
133 * @return int Current timestamp.
134 */
135 public static function currentTimestampUTC() {
136 $wp_timezone = wp_timezone();
137 $now = new DateTime( 'now', $wp_timezone ); // Current time in WP timezone.
138 return $now->getTimestamp();
139 }
140 /**
141 * Action.
142 *
143 * @param string $action action.
144 * @return void
145 */
146 public static function add_to_scheduled_hook_list( $action ) {
147 if ( empty( $action ) ) {
148 return;
149 }
150 $schedule = get_option( 'rtsb_cron_schedule_free', [] );
151 $schedule[] = $action;
152 update_option( 'rtsb_cron_schedule_free', array_unique( $schedule ) );
153 }
154 /**
155 * @return DB
156 */
157 public static function DB() {
158 return new DB( 'wpdb' );
159 }
160 /**
161 * Check if a plugin is installed.
162 *
163 * @param string $plugin_slug Plugin slug.
164 *
165 * @return bool
166 */
167 public static function check_plugin_installed( $plugin_slug ): bool {
168 $installed_plugins = get_plugins();
169
170 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
171 }
172
173 /**
174 * Check if a plugin is active.
175 *
176 * @param string $plugin_slug Plugin slug.
177 *
178 * @return bool
179 */
180 public static function check_plugin_active( $plugin_slug ): bool {
181 if ( is_plugin_active( $plugin_slug ) ) {
182 return true;
183 }
184
185 return false;
186 }
187 /**
188 * Get all user roles.
189 *
190 * @return array
191 */
192 public static function get_current_user_roles() {
193 $current_user = wp_get_current_user();
194 return $current_user->roles;
195 }
196 /**
197 * Get all user roles.
198 *
199 * @return array|false|mixed
200 */
201 public static function get_all_user_roles() {
202 $cache_key = 'rtsb_get_user_roles';
203 $roles = wp_cache_get( $cache_key, 'shopbuilder' );
204
205 if ( empty( $roles ) ) {
206 if ( ! function_exists( 'get_editable_roles' ) ) {
207 require_once ABSPATH . 'wp-admin/includes/user.php';
208 }
209
210 $user_roles = \get_editable_roles();
211 $roles = [];
212
213 foreach ( $user_roles as $key => $role ) {
214 if ( empty( $role['capabilities'] ) ) {
215 continue;
216 }
217 $roles[ $key ] = $role['name'];
218 }
219 }
220 wp_cache_set( $cache_key, $roles, 'shopbuilder' );
221 Cache::set_data_cache_key( $cache_key );
222 return $roles;
223 }
224
225 /**
226 * Stripslashes value.
227 *
228 * @param mixed $data Data.
229 *
230 * @return mixed|string
231 */
232 public static function stripslashes_value( $data ) {
233 if ( is_array( $data ) ) {
234 foreach ( $data as $key => $value ) {
235 $data[ $key ] = self::stripslashes_value( $value );
236 }
237 } elseif ( is_string( $data ) ) {
238 $trimmed = trim( $data );
239 // Try to decode JSON.
240 $json = json_decode( $trimmed, true );
241 if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
242 // Recursively stripslashes on decoded array.
243 $json = self::stripslashes_value( $json );
244 return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
245 }
246 // Not valid JSON, just stripslashes if needed.
247 if ( strpos( $data, '\\' ) !== false ) {
248 return stripslashes( $data );
249 }
250 }
251
252 return $data;
253 }
254
255 /**
256 * Multiselect settings field value
257 *
258 * @param string $string String.
259 *
260 * @return array
261 */
262 public static function multiselect_settings_field_value( $string ) {
263
264 $values = [];
265 if ( empty( $string ) ) {
266 return $values;
267 }
268
269 $stripslashes_data = self::stripslashes_value( $string );
270 if ( is_array( $stripslashes_data ) && count( $stripslashes_data ) ) {
271 foreach ( $stripslashes_data as $item ) {
272 $item = is_array( $item ) ? $item : json_decode( $item, true );
273 $values[] = $item['value'];
274 }
275 }
276
277 return $values;
278 }
279
280 /**
281 * Check if is block theme
282 *
283 * @return bool
284 */
285 public static function check_is_block_theme(): bool {
286 global $wp_version;
287
288 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
289 }
290
291 /**
292 * Locate template
293 *
294 * @param string $template_name Template name.
295 * @param string $template_path Template path. (default: '').
296 * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
297 *
298 * @return mixed|void
299 */
300 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
301 $template_name = self::sanitize_file_name( $template_name ) . '.php';
302
303 if ( ! $template_path ) {
304 $template_path = rtsb()->get_template_path();
305 }
306 if ( ! $plugin_path ) {
307 $plugin_path = RTSB_ABSPATH . 'templates/';
308 }
309
310 $template_rtsb_path = trailingslashit( $template_path ) . $template_name;
311 $template_path = '/' . $template_name;
312 $plugin_path = $plugin_path . $template_name;
313 $pro_plugin_path = rtsb()->has_pro() ? RTSBPRO_PATH . 'templates/' . $template_name : '';
314
315 $located = locate_template(
316 apply_filters(
317 'rtsb/core/locate_template_files',
318 [
319 $template_rtsb_path, // Search in <theme>/shopbuilder/.
320 $template_path, // Search in <theme>/.
321 ]
322 )
323 );
324
325 if ( ! $located ) {
326 if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
327 return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
328 } elseif ( file_exists( $plugin_path ) ) {
329 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
330 }
331 }
332
333 /**
334 * APPLY_FILTERS: rtsb/core/locate_template
335 *
336 * Filter the location of the templates.
337 *
338 * @param string $located Template found
339 * @param string $path Template path
340 *
341 * @return string
342 */
343 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
344 }
345
346 /**
347 * Remove any character that is not alphanumeric, /, _, or -.
348 *
349 * @param string $name Name to sanitize.
350 *
351 * @return array|string|string[]|null
352 */
353 private static function sanitize_file_name( $name ) {
354 return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
355 }
356
357 /**
358 * Template Content
359 *
360 * @param string $template_name Template name.
361 * @param array $args Arguments. (default: array).
362 * @param bool $return Whether to return or print the template.
363 * @param string $template_path Template path. (default: '').
364 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
365 *
366 * @return false|string|void
367 */
368 public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
369 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
370 $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
371 if ( ! $located ) {
372 $located = self::locate_template( $template_name, $template_path, $plugin_path );
373 // Don't cache the absolute path so that it can be shared between web servers with different paths.
374 $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
375 Cache::set_template_cache( $cache_key, $cache_path );
376 } else {
377 // Make sure that the absolute path to the template is resolved.
378 $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
379 }
380 if ( ! file_exists( $located ) ) {
381 // translators: %s template.
382 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
383
384 return;
385 }
386
387 if ( ! empty( $args ) && is_array( $args ) ) {
388 $atts = $args;
389 extract( $args ); // @codingStandardsIgnoreLine
390 }
391
392 // Allow 3rd party plugin filter template file from their plugin.
393 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
394
395 if ( $return ) {
396 ob_start();
397 }
398
399 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
400 include $located;
401
402 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
403
404 if ( $return ) {
405 return ob_get_clean();
406 }
407 }
408
409 /**
410 * Verify nonce.
411 *
412 * @return string
413 */
414 public static function is_woocommerce() {
415 return is_woocommerce() || is_cart() || is_checkout() || is_account_page();
416 }
417
418 /**
419 * Page builder
420 *
421 * @param int $post_id post id.
422 *
423 * @return string
424 */
425 public static function page_edit_with( $post_id ) {
426 if ( ! $post_id ) {
427 return '';
428 }
429
430 $edit_with = get_post_meta( $post_id, '_elementor_edit_mode', true );
431
432 if ( 'builder' === $edit_with ) {
433 $edit_by = 'elementor';
434 } else {
435 $edit_by = 'gutenberg';
436 }
437
438 return $edit_by;
439 }
440
441 /**
442 * Returns default expiration for wishlist cookie
443 *
444 * @return int Number of seconds the cookie should last.
445 */
446 public static function get_cookie_expiration() {
447 return intval( apply_filters( 'rtsb/cookie_expiration', 60 * 60 * 24 * 30 ) );
448 }
449
450 /**
451 * Create a cookie.
452 *
453 * @param string $name Cookie name.
454 * @param mixed $value Cookie value.
455 * @param int $time Cookie expiration time.
456 * @param bool $secure Whether cookie should be available to secured connection only.
457 * @param bool $httponly Whether cookie should be available to HTTP request only (no js handling).
458 *
459 * @return bool
460 * @since 1.0.0
461 */
462 public static function setcookie( $name, $value = [], $time = null, $secure = false, $httponly = false ) {
463
464 if ( ! apply_filters( 'rtsb/set_cookie', true ) || empty( $name ) ) {
465 return false;
466 }
467
468 $time = ! empty( $time ) ? $time : time() + self::get_cookie_expiration();
469
470 $value = wp_json_encode( stripslashes_deep( $value ) );
471 $expiration = apply_filters( 'rtsb/cookie_expiration_time', $time ); // Default 30 days.
472
473 $_COOKIE[ $name ] = $value;
474 wc_setcookie( $name, $value, $expiration, $secure, $httponly );
475
476 return true;
477 }
478
479 /**
480 * Retrieve the value of a cookie.
481 *
482 * @param string $name Cookie name.
483 *
484 * @return mixed
485 * @since 1.0.0
486 */
487 public static function getcookie( $name ) {
488 if ( isset( $_COOKIE[ $name ] ) ) {
489 return json_decode( sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ) ), true );
490 }
491
492 return [];
493 }
494
495 /**
496 * Woocommerce Last product id return
497 */
498 public static function get_prepared_product_id() {
499 if ( is_singular( 'product' ) ) {
500 return get_the_ID();
501 }
502 // Return the Builder Template id.
503
504 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
505 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
506 if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
507 return $product_id;
508 }
509 }
510
511 global $wpdb;
512 $cache_key = 'rtsb_prepared_product_id';
513 $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
514 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
515 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
516 $_post_id = $wpdb->get_var(
517 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
518 );
519 wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
520 Cache::set_data_cache_key( $cache_key );
521 }
522
523 return $_post_id;
524 }
525
526 /**
527 * Get the product function. Only used in single page widgets.
528 *
529 * @return object
530 */
531 public static function get_product() {
532 global $product;
533
534 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
535 return $product;
536 }
537 $cache_key = 'prepared_product_for_preview';
538 if ( isset( self::$cache[ $cache_key ] ) ) {
539 return self::$cache[ $cache_key ];
540 }
541 $product = wc_get_product( self::get_prepared_product_id() );
542 self::$cache[ $cache_key ] = $product;
543 do_action( 'rtsb_before_product_template_render' );
544 return $product;
545 }
546
547 /**
548 * Error function
549 *
550 * @param [type] $function function name.
551 * @param [type] $message message.
552 * @param [type] $version version.
553 *
554 * @return void
555 */
556 public static function doing_it_wrong( $function, $message, $version ) {
557 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
558 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
559 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
560 }
561
562 /**
563 * @return array
564 */
565 public static function get_pages() {
566 $pages = [];
567 $rawPages = get_pages();
568 if ( ! empty( $rawPages ) ) {
569 foreach ( $rawPages as $page ) {
570 $pages[ $page->ID ] = $page->post_title;
571 }
572 }
573
574 return $pages;
575 }
576
577 /**
578 * Get section items
579 *
580 * @param string $section_id Section ID.
581 *
582 * @return array
583 */
584 public static function get_section_items( $section_id ) {
585 if ( ! $section_id ) {
586 return [];
587 }
588
589 return DataModel::source()->get_option( $section_id, [] );
590 }
591
592 /**
593 * Get options items
594 *
595 * @param string $section_id Section ID.
596 * @param string $item_id Item ID.
597 *
598 * @return array
599 */
600 public static function get_options( $section_id, $item_id ) {
601 if ( ! $section_id || ! $item_id ) {
602 return [];
603 }
604 $sections = self::get_section_items( $section_id );
605
606 return isset( $sections[ $item_id ] ) ? $sections[ $item_id ] : [];
607 }
608
609 /**
610 * Get options value with default value if options doesn't exist.
611 *
612 * @param array $group Group.
613 * @param string $option_key Option key.
614 * @param string $default_value Default value.
615 *
616 * @return mixed|string
617 */
618 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
619
620 if ( ! $option_key || ! isset( $group[ $option_key ] ) ) {
621 return $default_value;
622 }
623
624 return $group[ $option_key ];
625 }
626
627 /**
628 * Get option.
629 *
630 * @param string $section_id Section ID.
631 * @param string $item_id Item ID.
632 * @param string $option_id Option ID.
633 * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
634 * @param null $type checkbox, multi_checkbox, number.
635 *
636 * @return bool|int|mixed|null
637 */
638 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
639 $options = self::get_options( $section_id, $item_id );
640
641 if ( 'checkbox' === $type ) {
642 if ( isset( $options[ $option_id ] ) ) {
643 return 'on' === $options[ $option_id ];
644 }
645
646 return $default;
647 } elseif ( 'multi_checkbox' === $type ) {
648 return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
649 } elseif ( 'number' === $type ) {
650 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
651 }
652
653 return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
654 }
655
656
657 /**
658 * Create a page and store the ID in an option.
659 *
660 * @param mixed $slug Slug for the new page.
661 * @param array $options ['section_id', 'item_id', 'option_id']Option name to store the page's ID.
662 * @param string $page_title (default: '') Title for the new page.
663 * @param string $page_content (default: '') Content for the new page.
664 * @param int $post_parent (default: 0) Parent for the new page.
665 * @param string $post_status (default: publish) The post status of the new page.
666 *
667 * @return int page ID.
668 */
669 public static function create_page( $slug, $options = '', $page_title = '', $page_content = '', $post_parent = 0, $post_status = 'publish' ) {
670 global $wpdb;
671
672 $option_value = 0;
673 if ( ! empty( $options ) ) {
674 if ( is_array( $options ) ) {
675 $options = wp_parse_args(
676 $options,
677 [
678 'section_id' => '',
679 'item_id' => '',
680 'option_id' => '',
681 ]
682 );
683 $option_value = self::get_option( $options['section_id'], $options['item_id'], $options['option_id'], 0, 'number' );
684 } else {
685 $option_value = absint( get_option( $options ) );
686 }
687 }
688
689 if ( $option_value > 0 ) {
690 $page_object = get_post( $option_value );
691
692 if ( $page_object && 'page' === $page_object->post_type && ! in_array(
693 $page_object->post_status,
694 [
695 'pending',
696 'trash',
697 'future',
698 'auto-draft',
699 ],
700 true
701 ) ) {
702 // Valid page is already in place.
703 return $page_object->ID;
704 }
705 }
706
707 if ( strlen( $page_content ) > 0 ) {
708 // Search for an existing page with the specified page content (typically a shortcode).
709 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
710 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
711 $valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_content LIKE %s LIMIT 1;", "%{$shortcode}%" ) );
712 } else {
713 // Search for an existing page with the specified page slug.
714 $valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_name = %s LIMIT 1;", $slug ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
715 }
716
717 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
718
719 if ( $valid_page_found ) {
720 if ( is_array( $options ) ) {
721 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
722 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
723 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $valid_page_found;
724 DataModel::source()->set_option( $options['section_id'], $section_items );
725 }
726 } else {
727 if ( $options ) {
728 update_option( $options, $valid_page_found );
729 }
730 }
731
732 return $valid_page_found;
733 }
734
735 // Search for a matching valid trashed page.
736 if ( strlen( $page_content ) > 0 ) {
737 // Search for an existing page with the specified page content (typically a shortcode).
738 $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
739 } else {
740 // Search for an existing page with the specified page slug.
741 $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
742 }
743
744 if ( $trashed_page_found ) {
745 $page_id = $trashed_page_found;
746 $page_data = [
747 'ID' => $page_id,
748 'post_status' => $post_status,
749 ];
750 wp_update_post( $page_data );
751 } else {
752 $page_data = [
753 'post_status' => $post_status,
754 'post_type' => 'page',
755 'post_author' => 1,
756 'post_name' => $slug,
757 'post_title' => $page_title,
758 'post_content' => $page_content,
759 'post_parent' => $post_parent,
760 'comment_status' => 'closed',
761 ];
762 $page_id = wp_insert_post( $page_data );
763
764 do_action( 'rtsb/core/page_created', $page_id, $page_data );
765 }
766
767 if ( is_array( $options ) ) {
768 if ( ! empty( $options['section_id'] ) && $options['item_id'] && $options['option_id'] ) {
769 $section_items = DataModel::source()->get_option( $options['section_id'], [] );
770 $section_items[ $options['item_id'] ][ $options['option_id'] ] = $page_id;
771 DataModel::source()->set_option( $options['section_id'], $section_items );
772 }
773 } else {
774 if ( $options ) {
775 update_option( $options, $page_id );
776 }
777 }
778
779 return $page_id;
780 }
781
782 /**
783 * Get allowed HTML tags.
784 *
785 * @return array
786 */
787 public static function get_kses_array() {
788 return [
789 'a' => [
790 'class' => [],
791 'href' => [],
792 'rel' => [],
793 'title' => [],
794 ],
795 'abbr' => [
796 'title' => [],
797 ],
798 'b' => [],
799 'blockquote' => [
800 'cite' => [],
801 ],
802 'cite' => [
803 'title' => [],
804 ],
805 'code' => [],
806 'del' => [
807 'datetime' => [],
808 'title' => [],
809 ],
810 'dd' => [],
811 'div' => [
812 'class' => [],
813 'title' => [],
814 'style' => [],
815 ],
816 'dl' => [],
817 'dt' => [],
818 'em' => [],
819 'h1' => [
820 'class' => [],
821 ],
822 'h2' => [
823 'class' => [],
824 ],
825 'h3' => [
826 'class' => [],
827 ],
828 'h4' => [
829 'class' => [],
830 ],
831 'h5' => [
832 'class' => [],
833 ],
834 'h6' => [
835 'class' => [],
836 ],
837 'i' => [
838 'class' => [],
839 ],
840 'img' => [
841 'alt' => [],
842 'class' => [],
843 'height' => [],
844 'src' => [],
845 'width' => [],
846 ],
847 'li' => [
848 'class' => [],
849 ],
850 'ol' => [
851 'class' => [],
852 ],
853 'p' => [
854 'class' => [],
855 ],
856 'q' => [
857 'cite' => [],
858 'title' => [],
859 ],
860 'span' => [
861 'class' => [],
862 'title' => [],
863 'style' => [],
864 ],
865 'iframe' => [
866 'width' => [],
867 'height' => [],
868 'scrolling' => [],
869 'frameborder' => [],
870 'allow' => [],
871 'src' => [],
872 ],
873 'strike' => [],
874 'br' => [],
875 'strong' => [],
876 'data-wow-duration' => [],
877 'data-wow-delay' => [],
878 'data-wallpaper-options' => [],
879 'data-stellar-background-ratio' => [],
880 'ul' => [
881 'class' => [],
882 ],
883 ];
884 }
885
886
887 /**
888 * Escape output of wishlist icon
889 *
890 * @param string $data Data to escape.
891 *
892 * @return void
893 */
894 public static function print_icon( $data ) {
895 /**
896 * APPLY_FILTERS: rtsb/core/allowed_icon_html
897 *
898 * Filter the allowed HTML for the icons.
899 *
900 * @param array $allowed_icon_html Allowed HTML
901 *
902 * @return array
903 */
904 $allowed_icon_html = apply_filters(
905 'rtsb/core/allowed_icon_html',
906 [
907 'i' => [
908 'class' => true,
909 ],
910 'img' => [
911 'src' => true,
912 'alt' => true,
913 'width' => true,
914 'height' => true,
915 ],
916 'svg' => [
917 'class' => true,
918 'aria-hidden' => true,
919 'aria-labelledby' => true,
920 'role' => true,
921 'xmlns' => true,
922 'width' => true,
923 'height' => true,
924 'viewbox' => true,
925 'stroke' => true,
926 'fill' => true,
927 ],
928 'g' => [
929 'fill' => true,
930 ],
931 'title' => [
932 'title' => true,
933 ],
934 'path' => [
935 'd' => true,
936 'fill' => true,
937 'stroke' => true,
938 'stroke-width' => true,
939 'stroke-linecap' => true,
940 'stroke-linejoin' => true,
941 'fill-rule' => true,
942 'clip-rule' => true,
943 ],
944 ]
945 );
946
947 echo wp_kses( $data, $allowed_icon_html );
948 }
949
950 /**
951 * Prints HTMl.
952 *
953 * @param string $html HTML.
954 * @param bool $allHtml All HTML.
955 *
956 * @return void
957 */
958 public static function print_html( $html, $allHtml = false ) {
959 if ( ! $html ) {
960 return;
961 }
962 if ( $allHtml ) {
963 echo stripslashes_deep( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
964 } else {
965 echo wp_kses_post( stripslashes_deep( $html ) );
966 }
967 }
968
969 /**
970 * Allowed HTML for wp_kses.
971 *
972 * @param string $level Tag level.
973 *
974 * @return mixed
975 */
976 public static function allowedHtml( $level = 'basic' ) {
977 $allowed_html = [];
978
979 switch ( $level ) {
980 case 'basic':
981 $allowed_html = [
982 'b' => [
983 'class' => [],
984 'id' => [],
985 ],
986 'i' => [
987 'class' => [],
988 'id' => [],
989 ],
990 'u' => [
991 'class' => [],
992 'id' => [],
993 ],
994 'br' => [
995 'class' => [],
996 'id' => [],
997 ],
998 'em' => [
999 'class' => [],
1000 'id' => [],
1001 ],
1002 'span' => [
1003 'class' => [],
1004 'id' => [],
1005 ],
1006 'strong' => [
1007 'class' => [],
1008 'id' => [],
1009 ],
1010 'hr' => [
1011 'class' => [],
1012 'id' => [],
1013 ],
1014 'div' => [
1015 'class' => [],
1016 'id' => [],
1017 ],
1018 'a' => [
1019 'href' => [],
1020 'title' => [],
1021 'class' => [],
1022 'id' => [],
1023 'target' => [],
1024 ],
1025 ];
1026 break;
1027
1028 case 'advanced':
1029 $allowed_html = [
1030 'b' => [
1031 'class' => [],
1032 'id' => [],
1033 ],
1034 'i' => [
1035 'class' => [],
1036 'id' => [],
1037 ],
1038 'u' => [
1039 'class' => [],
1040 'id' => [],
1041 ],
1042 'br' => [
1043 'class' => [],
1044 'id' => [],
1045 ],
1046 'em' => [
1047 'class' => [],
1048 'id' => [],
1049 ],
1050 'span' => [
1051 'class' => [],
1052 'id' => [],
1053 ],
1054 'strong' => [
1055 'class' => [],
1056 'id' => [],
1057 ],
1058 'hr' => [
1059 'class' => [],
1060 'id' => [],
1061 ],
1062 'a' => [
1063 'href' => [],
1064 'title' => [],
1065 'class' => [],
1066 'id' => [],
1067 'target' => [],
1068 ],
1069 'input' => [
1070 'type' => [],
1071 'name' => [],
1072 'class' => [],
1073 'value' => [],
1074 ],
1075 ];
1076 break;
1077
1078 case 'image':
1079 $allowed_html = [
1080 'img' => [
1081 'src' => [],
1082 'data-src' => [],
1083 'alt' => [],
1084 'height' => [],
1085 'width' => [],
1086 'class' => [],
1087 'id' => [],
1088 'style' => [],
1089 'srcset' => [],
1090 'loading' => [],
1091 'sizes' => [],
1092 ],
1093 'div' => [
1094 'class' => [],
1095 ],
1096 ];
1097 break;
1098
1099 case 'anchor':
1100 $allowed_html = [
1101 'a' => [
1102 'href' => [],
1103 'title' => [],
1104 'class' => [],
1105 'id' => [],
1106 'style' => [],
1107 ],
1108 ];
1109 break;
1110
1111 default:
1112 // code...
1113 break;
1114 }
1115
1116 return $allowed_html;
1117 }
1118
1119 /**
1120 * Safe get a validated HTML tag.
1121 *
1122 * @param string $tag HTML tag.
1123 *
1124 * @return string
1125 */
1126 public static function get_validated_html_tag( $tag ) {
1127 $allowed_html_wrapper_tags = [
1128 'a',
1129 'article',
1130 'aside',
1131 'button',
1132 'div',
1133 'footer',
1134 'h1',
1135 'h2',
1136 'h3',
1137 'h4',
1138 'h5',
1139 'h6',
1140 'header',
1141 'main',
1142 'nav',
1143 'p',
1144 'section',
1145 'span',
1146 ];
1147
1148 return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1149 }
1150
1151 /**
1152 * Safe print a validated HTML tag.
1153 *
1154 * @param string $tag HTML tag.
1155 *
1156 * @return void
1157 */
1158 public static function print_validated_html_tag( $tag ) {
1159 self::print_html( self::get_validated_html_tag( $tag ) );
1160 }
1161
1162 /**
1163 * Insert Some array element
1164 *
1165 * @param [type] $key The elements will insert nearby this key.
1166 * @param [type] $main_array Original array.
1167 * @param [type] $insert_array some element will insert in original array.
1168 * @param boolean $is_after array insert position base on the key.
1169 *
1170 * @return array
1171 */
1172 public static function insert_controls( $key, $main_array, $insert_array, $is_after = false ) {
1173 $index = array_search( $key, array_keys( $main_array ), true );
1174 if ( 'integer' === gettype( $index ) ) {
1175 if ( $is_after ) {
1176 $index++;
1177 }
1178 $main_array = array_merge(
1179 array_slice( $main_array, 0, $index ),
1180 $insert_array,
1181 array_slice( $main_array, $index )
1182 );
1183 }
1184
1185 return $main_array;
1186 }
1187
1188 /**
1189 * Image Sizes
1190 *
1191 * @return array
1192 */
1193 public static function get_image_sizes() {
1194 global $_wp_additional_image_sizes;
1195
1196 $sizes = [];
1197
1198 foreach ( get_intermediate_image_sizes() as $_size ) {
1199 if ( in_array( $_size, [ 'thumbnail', 'medium', 'large' ], true ) ) {
1200 $sizes[ $_size ]['width'] = get_option( "{$_size}_size_w" );
1201 $sizes[ $_size ]['height'] = get_option( "{$_size}_size_h" );
1202 $sizes[ $_size ]['crop'] = (bool) get_option( "{$_size}_crop" );
1203 } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
1204 $sizes[ $_size ] = [
1205 'width' => $_wp_additional_image_sizes[ $_size ]['width'],
1206 'height' => $_wp_additional_image_sizes[ $_size ]['height'],
1207 'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
1208 ];
1209 }
1210 }
1211
1212 $imgSize = [];
1213
1214 foreach ( $sizes as $key => $img ) {
1215 $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1216 }
1217
1218 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1219 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
1220
1221 return $imgSize;
1222 }
1223
1224 /**
1225 * Free Layouts
1226 *
1227 * @param string $layout Layout.
1228 *
1229 * @return array
1230 */
1231 public static function free_layouts( $layout = '' ) {
1232 if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1233 return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1234 }
1235
1236 $layouts = [
1237 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1238 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1239 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
1240 'list-layout2' => esc_html__( 'List Layout 2', 'shopbuilder' ),
1241 'slider-layout1' => esc_html__( 'Slider Layout 1', 'shopbuilder' ),
1242 'slider-layout2' => esc_html__( 'Slider Layout 2', 'shopbuilder' ),
1243 'category-single-layout1' => esc_html__( 'Category Single Layout 1', 'shopbuilder' ),
1244 'category-layout1' => esc_html__( 'Category Layout 1', 'shopbuilder' ),
1245 'category-layout2' => esc_html__( 'Category Layout 2', 'shopbuilder' ),
1246 ];
1247
1248 return apply_filters( 'rtsb/elements/elementor/free_layouts', $layouts );
1249 }
1250
1251 /**
1252 * Get all terms by taxonomy
1253 *
1254 * @param string $taxonomy Taxonomy name.
1255 * @param bool $placeholder Placeholder..
1256 *
1257 * @return array
1258 */
1259 public static function get_all_terms( $taxonomy, $placeholder = false ) {
1260 $terms = [];
1261
1262 if ( empty( $taxonomy ) ) {
1263 return $terms;
1264 }
1265 $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1266 if ( isset( self::$cache[ $cache_key ] ) ) {
1267 return self::$cache[ $cache_key ];
1268 }
1269
1270 $termList = get_terms(
1271 [
1272 'taxonomy' => $taxonomy,
1273 'hide_empty' => false,
1274 ]
1275 );
1276
1277 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
1278 if ( $placeholder ) {
1279 $terms = [
1280 'all' => __( 'All Products', 'shopbuilder' ),
1281 ];
1282 }
1283
1284 foreach ( $termList as $term ) {
1285 $terms[ $term->term_id ] = esc_html( $term->name );
1286 }
1287 }
1288 self::$cache[ $cache_key ] = $terms;
1289 return $terms;
1290 }
1291 /**
1292 * Get all product attribute taxonomy by id
1293 *
1294 * @param array $term_ids Term id.
1295 *
1296 * @return array
1297 */
1298 public static function tax_filter_attr_taxonomy( $term_ids ) {
1299 $taxonomies = [];
1300 foreach ( $term_ids as $id ) {
1301 $term = get_term( $id );
1302 if ( ! is_wp_error( $term ) && $term ) {
1303 $taxonomies[] = $term->taxonomy;
1304 }
1305 }
1306 return array_unique( $taxonomies );
1307 }
1308
1309 /**
1310 * Get all terms by attributes
1311 *
1312 * @return array
1313 */
1314 public static function get_all_attributes() {
1315 $terms = [];
1316 $termList = [];
1317
1318 $attributes = wc_get_attribute_taxonomies();
1319
1320 if ( $attributes ) {
1321 foreach ( $attributes as $tax ) {
1322 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1323 $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1324 }
1325 }
1326 }
1327
1328 if ( ! empty( $termList ) && empty( $termList['errors'] ) && is_array( $termList ) ) {
1329 foreach ( $termList as $name => $atts ) {
1330 foreach ( $atts as $term ) {
1331 $terms[ $term->term_id ] = esc_html( ucwords( str_replace( '-', ' ', $name ) ) . ' - ' . $term->name );
1332 }
1333 }
1334 }
1335
1336 return $terms;
1337 }
1338
1339 /**
1340 * Get all attributes name
1341 *
1342 * @return array
1343 */
1344 public static function get_all_attributes_name() {
1345 $attributes_name = [];
1346
1347 $attributes = wc_get_attribute_taxonomies();
1348
1349 if ( $attributes ) {
1350 foreach ( $attributes as $tax ) {
1351 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1352 $attributes_name[ wc_attribute_taxonomy_name( $tax->attribute_name ) ] = ucwords( $tax->attribute_name );
1353 }
1354 }
1355 }
1356
1357 return $attributes_name;
1358 }
1359
1360 /**
1361 * Get User list
1362 *
1363 * @return array
1364 */
1365 public static function get_users() {
1366 $users = [];
1367 $u = get_users();
1368
1369 if ( ! empty( $u ) ) {
1370 foreach ( $u as $user ) {
1371 $users[ $user->ID ] = $user->display_name;
1372 }
1373 }
1374
1375 return $users;
1376 }
1377
1378 /**
1379 * Get Taxonomy List.
1380 *
1381 * @return array
1382 */
1383 public static function get_tax_list() {
1384 return apply_filters(
1385 'rtsb/elements/tax_list',
1386 [
1387 'product_cat' => esc_html__( 'Product Category', 'shopbuilder' ),
1388 ]
1389 );
1390 }
1391
1392 /**
1393 * Get Category Query List.
1394 *
1395 * @return array
1396 */
1397 public static function get_cat_list() {
1398 return [
1399 'all' => esc_html__( 'All Categories', 'shopbuilder' ),
1400 'specific_parent' => esc_html__( 'Sub-Categories by Parent', 'shopbuilder' ),
1401 'cat_ids' => esc_html__( 'Select by ID', 'shopbuilder' ),
1402 'selection' => esc_html__( 'Manual Selection', 'shopbuilder' ),
1403 ];
1404 }
1405
1406 /**
1407 * Get Term List.
1408 *
1409 * @param string $taxonomy Taxonomy.
1410 * @param bool $first_term First term.
1411 * @param bool $only_parents Only parent terms.
1412 * @param bool $return_slug Return with slug.
1413 *
1414 * @return int|array
1415 */
1416 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1417 if ( ! is_admin() ) {
1418 return [];
1419 }
1420
1421 $term_list = [];
1422 $args = [
1423 'taxonomy' => $taxonomy,
1424 'hide_empty' => false,
1425 ];
1426
1427 if ( $only_parents ) {
1428 $args['parent'] = 0;
1429 }
1430
1431 $terms = get_terms( $args );
1432
1433 if ( empty( $terms ) ) {
1434 return [ esc_html__( 'Nothing found', 'shopbuilder' ) ];
1435 }
1436
1437 foreach ( $terms as $term ) {
1438 if ( $return_slug ) {
1439 $term_list[ $term->slug ] = $term->name;
1440 } else {
1441 $term_list[ $term->term_id ] = $term->name;
1442 }
1443 }
1444
1445 if ( $first_term ) {
1446 return array_keys( $term_list )[0];
1447 } else {
1448 return $term_list;
1449 }
1450 }
1451
1452 /**
1453 * Get product rating.
1454 *
1455 * @param array $args Arguments.
1456 *
1457 * @return string|void
1458 */
1459 public static function get_product_rating_html( $args = [] ) {
1460 global $product;
1461
1462 $html = '';
1463 $rating_count = $product->get_rating_count();
1464 $average_rating = $product->get_average_rating();
1465
1466 if ( ! rtsb()->has_pro() || empty( $args ) ) {
1467 if ( ! $rating_count || empty( wc_get_rating_html( $average_rating, $rating_count ) ) ) {
1468 return $html;
1469 }
1470
1471 $html .= '<div class="product-rating">';
1472 $html .= wc_get_rating_html( $average_rating, $rating_count );
1473 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1474 $html .= '</div>';
1475
1476 self::print_html( $html, true );
1477
1478 return;
1479 }
1480
1481 if ( empty( $rating_count ) && ! $args['show_empty_rating'] ) {
1482 return '';
1483 }
1484
1485 $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1486 $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1487 $count = '';
1488
1489 if ( $args['show_rating_count'] ) {
1490 $count .= '<div class="rtsb-count">';
1491 $count .= sprintf(
1492 /* translators: %s is the number of reviews */
1493 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1494 $rating_count
1495 );
1496 $count .= '</div>';
1497 }
1498
1499 if ( 'preset2' === $preset ) {
1500 $average = $args['show_average_rating'] ? '<div class="inner-wrapper"><span class="star-icon"></span><span class="average-rating">' . $average_rating . '</span></div>' : '';
1501 }
1502
1503 $html .= '<div class="product-rating ' . esc_attr( $preset ) . '">';
1504
1505 if ( 'preset1' === $preset ) {
1506 $html .= wc_get_rating_html( $average_rating, $rating_count );
1507 $html .= $average;
1508 $html .= $count;
1509 } elseif ( 'preset2' === $preset ) {
1510 $html .= $average;
1511 $html .= $count;
1512 }
1513
1514 $html .= '</div>';
1515
1516 self::print_html( $html, true );
1517 }
1518
1519 /**
1520 * Get product simple rating.
1521 *
1522 * @return void
1523 */
1524 public static function get_product_simple_rating_html() {
1525 global $product;
1526 $html = null;
1527 $rating_count = $product->get_rating_count();
1528 $average_rating = $product->get_average_rating();
1529 $html .= '<div class="product-rating">';
1530 $html .= wc_get_rating_html( $average_rating, $rating_count );
1531 $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1532 $html .= '</div>';
1533
1534 self::print_html( $html, true );
1535 }
1536
1537
1538
1539 /**
1540 * Text truncation.
1541 *
1542 * @param string $text_to_truncate Text.
1543 * @param int $limit Limit.
1544 * @param string $after After text.
1545 *
1546 * @return string
1547 */
1548 public static function text_truncation( $text_to_truncate, $limit, $after = '&#8230;' ) {
1549 if ( empty( $limit ) ) {
1550 return $text_to_truncate;
1551 }
1552
1553 $limit++;
1554
1555 $text = '';
1556
1557 if ( mb_strlen( $text_to_truncate ) > $limit ) {
1558 $subex = mb_substr( wp_strip_all_tags( $text_to_truncate ), 0, $limit );
1559 $exwords = explode( ' ', $subex );
1560 $excut = - ( mb_strlen( $exwords[ count( $exwords ) - 1 ] ) );
1561
1562 if ( $excut < 0 ) {
1563 $text .= mb_substr( $subex, 0, $excut ) . $after;
1564 } else {
1565 $text .= $subex . $after;
1566 }
1567 } else {
1568 $text .= $text_to_truncate;
1569 }
1570
1571 return $text;
1572 }
1573
1574 /**
1575 * Promo Badge HTML
1576 *
1577 * @param string $text Text.
1578 * @param string $class Class.
1579 *
1580 * @return void
1581 */
1582 public static function get_badge_html( $text, $class = 'fill' ) {
1583 if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1584 do_action( 'rtsb/modules/product_badges/frontend/display' );
1585
1586 return;
1587 }
1588
1589 if ( empty( $text ) ) {
1590 return;
1591 }
1592 ob_start();
1593 ?>
1594
1595 <ul class="rtsb-promotion-list">
1596 <li class="rtsb-promotion-list-item">
1597 <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1598 </li>
1599 </ul>
1600
1601 <?php
1602 self::print_html( ob_get_clean() );
1603 }
1604
1605 /**
1606 * Categories HTML
1607 *
1608 * @param int $id Product ID.
1609 * @param string $class Custom class.
1610 *
1611 * @return void|string
1612 */
1613 public static function get_categories_list( $id, $class = 'rtsb-category-outline' ) {
1614 if ( empty( $id ) ) {
1615 return '';
1616 }
1617
1618 ob_start();
1619 ?>
1620
1621 <ul class="rtsb-category-list <?php echo esc_attr( $class ); ?>">
1622 <?php
1623 self::print_html(
1624 wc_get_product_category_list(
1625 $id,
1626 '</li><li class="rtsb-category-list-item">',
1627 '<li class="rtsb-category-list-item">',
1628 '</li>'
1629 )
1630 );
1631 ?>
1632 </ul>
1633
1634 <?php
1635 self::print_html( ob_get_clean() );
1636 }
1637 /**
1638 * Brands HTML
1639 *
1640 * @param int $id Product ID.
1641 * @param string $class Custom class.
1642 *
1643 * @return void|string
1644 */
1645 public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1646 $terms = get_the_terms( $id, 'product_brand' );
1647 if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1648 return '';
1649 }
1650 ob_start();
1651 ?>
1652
1653 <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1654 <?php
1655 $brand_list = get_the_term_list(
1656 $id,
1657 'product_brand',
1658 '<li class="rtsb-brand-list-item">',
1659 '</li><li class="rtsb-brand-list-item">',
1660 '</li>'
1661 );
1662
1663 self::print_html( $brand_list );
1664 ?>
1665 </ul>
1666
1667 <?php
1668 self::print_html( ob_get_clean() );
1669 }
1670
1671 /**
1672 * Get Featured Image HTML.
1673 *
1674 * @param string $type Image type.
1675 * @param int $post_id Post ID.
1676 * @param string $f_img_size Image size.
1677 * @param null $default_img_id Default image ID.
1678 * @param array $custom_img_size Custom image size.
1679 * @param bool $lazy Lazy load check.
1680 * @param bool $hover Hover image.
1681 * @param bool $gallery Gallery image.
1682 * @param bool $custom_image_id Custom category image ID.
1683 *
1684 * @return string|null
1685 */
1686 public static function get_product_image_html( $type = 'product', $post_id = null, $f_img_size = 'medium', $default_img_id = null, $custom_img_size = [], $lazy = false, $hover = false, $gallery = false, $custom_image_id = 0 ) {
1687 $img_html = null;
1688 $attachment_id = null;
1689 $c_size = false;
1690 $hover_class = '';
1691 $post_title = '';
1692
1693 if ( 'rtsb_custom' === $f_img_size ) {
1694 $f_img_size = 'full';
1695 $c_size = true;
1696 }
1697
1698 if ( ! rtsb()->has_pro() ) {
1699 $gallery = false;
1700 }
1701
1702 if ( $hover ) {
1703 $a_id = $post_id;
1704 $hover_class = ' rtsb-img-hover';
1705 } elseif ( $gallery ) {
1706 $a_id = $post_id;
1707 } else {
1708 if ( 'product' === $type ) {
1709 $a_id = get_post_thumbnail_id( $post_id );
1710 $post_title = get_the_title( $post_id );
1711 } elseif ( 'category' === $type ) {
1712 $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1713 $post_title = get_term( $post_id )->name;
1714 } else {
1715 $a_id = $default_img_id;
1716 }
1717 }
1718
1719 $img_alt = trim( wp_strip_all_tags( get_post_meta( $a_id, '_wp_attachment_image_alt', true ) ) );
1720 $alt_tag = ! empty( $img_alt ) ? $img_alt : wp_strip_all_tags( $post_title );
1721 $lazy_class = $lazy ? ' swiper-lazy' : '';
1722 $attr = [
1723 'class' => 'img-responsive rtsb-product-image' . $lazy_class . $hover_class,
1724 'alt' => $alt_tag,
1725 ];
1726
1727 if ( $a_id ) {
1728 $img_html = wp_get_attachment_image( $a_id, $f_img_size, false, $attr );
1729 $attachment_id = $a_id;
1730 }
1731
1732 if ( ! $img_html && $default_img_id ) {
1733 $img_html = wp_get_attachment_image( $default_img_id, $f_img_size, false, $attr );
1734 $attachment_id = $default_img_id;
1735 }
1736
1737 if ( $img_html && $c_size ) {
1738 preg_match( '@src="([^"]+)"@', $img_html, $match );
1739 $img_src = array_pop( $match );
1740 $w = ! empty( $custom_img_size['width'] ) ? absint( $custom_img_size['width'] ) : null;
1741 $h = ! empty( $custom_img_size['height'] ) ? absint( $custom_img_size['height'] ) : null;
1742 $c = ! empty( $custom_img_size['crop'] ) && 'soft' === $custom_img_size['crop'] ? false : true;
1743
1744 if ( $w && $h ) {
1745 $image = self::image_resize( $img_src, $w, $h, $c, false );
1746
1747 if ( ! empty( $image ) ) {
1748 [ $src, $width, $height ] = $image;
1749
1750 $hwstring = image_hwstring( $width, $height );
1751 $attachment = get_post( $attachment_id );
1752 $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $f_img_size );
1753
1754 if ( $lazy ) {
1755 $attr['data-src'] = $src;
1756 } else {
1757 $attr['src'] = $src;
1758 }
1759
1760 $attr = array_map( 'esc_attr', $attr );
1761 $img_html = rtrim( "<img $hwstring" );
1762
1763 foreach ( $attr as $name => $value ) {
1764 $img_html .= " $name=" . '"' . $value . '"';
1765 }
1766
1767 $img_html .= ' />';
1768 }
1769 }
1770 }
1771
1772 if ( ! $img_html ) {
1773 $hwstring = image_hwstring( 160, 160 );
1774 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1775 $attr['class'] = 'default-img ' . $hover_class;
1776 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1777 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1778 $img_html = rtrim( "<img $hwstring" );
1779
1780 foreach ( $attr as $name => $value ) {
1781 $img_html .= " $name=" . '"' . $value . '"';
1782 }
1783
1784 $img_html .= ' />';
1785 }
1786
1787 if ( $lazy ) {
1788 $img_html = $img_html . '<div class="swiper-lazy-preloader swiper-lazy-preloader"></div>';
1789 }
1790
1791 return $img_html;
1792 }
1793
1794 /**
1795 * Call the Image resize model for resize function
1796 *
1797 * @param string $url URL.
1798 * @param int $width Width.
1799 * @param int $height Height.
1800 * @param string $crop Crop.
1801 * @param bool|true $single Single.
1802 * @param bool|false $upscale Upscale.
1803 *
1804 * @return array|bool|string
1805 */
1806 public static function image_resize( $url, $width = null, $height = null, $crop = null, $single = true, $upscale = false ) {
1807 $rtResize = new ReSizer();
1808
1809 return $rtResize->process( $url, $width, $height, $crop, $single, $upscale );
1810 }
1811
1812 /**
1813 * Get Product Image
1814 *
1815 * @param string $f_image Featured Image.
1816 * @param string $h_image Hover Image.
1817 *
1818 * @return void
1819 */
1820 public static function get_product_image( $f_image, $h_image = null ) {
1821 if ( empty( $f_image ) ) {
1822 return;
1823 }
1824
1825 echo wp_kses( $f_image, self::allowedHtml( 'image' ) );
1826
1827 if ( ! empty( $h_image ) ) {
1828 echo wp_kses( $h_image, self::allowedHtml( 'image' ) );
1829 }
1830 }
1831
1832 /**
1833 * Post Custom Field value.
1834 *
1835 * @param int $post_id Post id.
1836 * @param string $field_key Custom Field Key.
1837 * @param string $field_fallback FallBack text.
1838 *
1839 * @return string|void
1840 */
1841 public static function get_post_custom_field_value( $post_id, $field_key = '', $field_fallback = '' ) {
1842 if ( ! $post_id || ! $field_key ) {
1843 return;
1844 }
1845 $field_value = get_post_meta( $post_id, $field_key, true );
1846 if ( empty( $field_value ) ) {
1847 $field_value = ! empty( $field_fallback ) ? $field_fallback : $field_value;
1848 }
1849 $field_value = apply_filters( 'rtsb/get_post_custom_field_value/' . $field_key, $field_value, $field_key );
1850
1851 return sprintf( '<span class="rtsb-woo-custom-field">%s</span>', $field_value );
1852 }
1853
1854 /**
1855 * Elementor Icon
1856 *
1857 * @param array $control Elementor Control array.
1858 * @param string $class Custom class.
1859 * @param string $builder Builder name.
1860 *
1861 * @return string
1862 */
1863 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
1864 if ( empty( $control['value'] ) ) {
1865 return '';
1866 }
1867 if ( is_array( $control['value'] ) ) {
1868 $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1869 } else {
1870 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1871 }
1872 if ( isset( self::$cache[ $cache_key ] ) ) {
1873 return self::$cache[ $cache_key ];
1874 }
1875
1876 $attributes = [
1877 'aria-hidden' => 'true',
1878 ];
1879
1880 if ( ! empty( $class ) ) {
1881 $attributes['class'] = esc_attr( $class );
1882 }
1883
1884 ob_start();
1885
1886 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
1887 Icons_Manager::render_icon( $control, $attributes );
1888 }
1889
1890 $icons = ob_get_clean();
1891 self::$cache[ $cache_key ] = $icons;
1892 return $icons;
1893 }
1894
1895 /**
1896 * Get product swatches.
1897 *
1898 * @param string $type Swatch type.
1899 *
1900 * @return void
1901 */
1902 public static function get_product_swatches( $type ) {
1903 ?>
1904 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1905 <?php
1906 if ( class_exists( 'Rtwpvsp' ) ) {
1907 do_action( 'rtwpvs_show_archive_variation' );
1908 } else {
1909 do_action( 'rtsb/vs/showcase/variation' );
1910 }
1911 ?>
1912 </div>
1913 <?php
1914 }
1915
1916 /**
1917 * Get sale badge
1918 *
1919 * @param string $type Badge type.
1920 * @param string $text Badge text.
1921 * @param string $out_of_stock_text Out of stock text.
1922 *
1923 * @return string
1924 */
1925 public static function get_sale_badge( $type, $text, $out_of_stock_text ) {
1926 global $product;
1927
1928 if ( ! $product->is_in_stock() ) {
1929 return $out_of_stock_text;
1930 }
1931
1932 if ( ! $product->is_on_sale() ) {
1933 return '';
1934 }
1935
1936 $badge_text = '';
1937 $percentage = self::calculate_sale_percentage( $product );
1938
1939 if ( $percentage > 0 ) {
1940 $badge_text = '-' . round( $percentage ) . '%';
1941 }
1942
1943 if ( 'text' === $type ) {
1944 $badge_text = $text;
1945 }
1946
1947 return $badge_text;
1948 }
1949
1950 /**
1951 * Get sale badge
1952 *
1953 * @param object $product Product Object.
1954 * @param string $type Badge type.
1955 * @param string $text Badge text.
1956 * @param string $out_of_stock_text Out of stock text.
1957 *
1958 * @return string
1959 */
1960 public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
1961 $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
1962 $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1963
1964 if ( $badges_visibility ) {
1965 return '';
1966 }
1967
1968 if ( is_null( $product ) ) {
1969 global $product;
1970 }
1971
1972 if ( ! $product instanceof WC_Product ) {
1973 return '';
1974 }
1975
1976 if ( ! $product->is_in_stock() ) {
1977 return $out_of_stock_text;
1978 }
1979
1980 if ( ! $product->is_on_sale() ) {
1981 return '';
1982 }
1983
1984 $badge_text = '';
1985 $percentage = self::calculate_sale_percentage( $product );
1986
1987 if ( $percentage > 0 ) {
1988 $badge_text = '-' . round( $percentage ) . '%';
1989 }
1990
1991 if ( 'text' === $type ) {
1992 $badge_text = $text;
1993 }
1994
1995 return $badge_text;
1996 }
1997
1998 /**
1999 * Calculate Sale Percentage
2000 *
2001 * @param object $product Product object.
2002 *
2003 * @return float|int|string
2004 */
2005 public static function calculate_sale_percentage( $product = null ) {
2006 if ( is_null( $product ) ) {
2007 global $product;
2008 }
2009 if ( ! $product instanceof WC_Product ) {
2010 return '';
2011 }
2012 if ( ! $product->is_on_sale() ) {
2013 return '';
2014 }
2015 $max_percentage = 0;
2016 if ( $product->is_type( 'simple' ) ) {
2017 $regular_price = (float) $product->get_regular_price();
2018 $sale_price = (float) $product->get_sale_price();
2019 if ( $regular_price > 0 && $sale_price > 0 ) {
2020 $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2021 }
2022 } elseif ( $product->is_type( 'variable' ) ) {
2023 $prices = $product->get_variation_prices( true );
2024 if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
2025 foreach ( $prices['regular_price'] as $key => $regular_price ) {
2026 $sale_price = $prices['sale_price'][ $key ];
2027
2028 if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
2029 $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2030 $max_percentage = max( $max_percentage, $percentage );
2031 }
2032 }
2033 }
2034 }
2035 return $max_percentage > 0 ? round( $max_percentage ) : 0;
2036 }
2037
2038 /**
2039 * Pagination
2040 *
2041 * @param string $pages Pages.
2042 * @param integer $range Range.
2043 * @param boolean $ajax Ajax.
2044 *
2045 * @return string
2046 */
2047 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
2048 $html = null;
2049 $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
2050
2051 global $paged;
2052
2053 if ( is_front_page() ) {
2054 $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2055 } else {
2056 $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2057 }
2058
2059 if ( empty( $paged ) ) {
2060 $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2061 }
2062
2063 if ( '' === $pages ) {
2064 global $wp_query;
2065
2066 $pages = $wp_query->max_num_pages;
2067
2068 if ( ! $pages ) {
2069 $pages = 1;
2070 }
2071 }
2072
2073 $ajaxClass = null;
2074 $dataAttr = null;
2075
2076 if ( $ajax ) {
2077 $ajaxClass = ' rtsb-pagination-ajax';
2078 $dataAttr = "data-paged='1'";
2079 $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
2080 }
2081
2082 if ( 1 !== $pages ) {
2083 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
2084 $html .= '<ul class="pagination-list">';
2085
2086 if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
2087 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
2088 }
2089
2090 if ( $paged > 1 && $visible_range < $pages ) {
2091 $p = $paged - 1;
2092 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
2093 }
2094
2095 for ( $i = 1; $i <= $pages; $i++ ) {
2096 if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
2097 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
2098 }
2099 }
2100
2101 if ( $paged < $pages && $visible_range < $pages ) {
2102 $p = $paged + 1;
2103 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
2104 }
2105
2106 if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
2107 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
2108 }
2109
2110 $html .= '</ul>';
2111 $html .= '</div>';
2112 }
2113
2114 return $html;
2115 }
2116
2117 /**
2118 * Action Buttons HTMl
2119 *
2120 * @param array $items Items.
2121 * @param array $ajax_cart Ajax cart HTML.
2122 * @param string $preset Button preset.
2123 * @param array $position Position.
2124 * @param string $placement Placement.
2125 *
2126 * @return void|string
2127 */
2128 public static function get_formatted_action_buttons( $items, $ajax_cart = '', $preset = 'preset1', $position = 'above', $placement = 'top' ) {
2129 if ( empty( $items ) ) {
2130 return;
2131 }
2132
2133 $html = '';
2134 $class = '';
2135 $args = [ $items, $ajax_cart, $preset, $position, $placement ];
2136
2137 if ( ( 'top' === $placement && 'after' === $position ) || ( 'bottom' === $placement && 'above' === $position ) ) {
2138 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
2139 }
2140
2141 if ( 'preset2' === $preset ) {
2142 $class = 'rtsb-action-buttons-vertical vertical-delay-effect ' . $preset;
2143 } elseif ( 'preset4' === $preset ) {
2144 $class = 'rtsb-action-buttons-vertical rtsb-action-buttons-vertical-left vertical-delay-effect ' . $preset;
2145 } elseif ( 'preset1' === $preset || 'preset3' === $preset ) {
2146 $class = 'rtsb-action-buttons-cart-box-width-auto horizontal-floating-btn ' . $preset;
2147 }
2148
2149 if ( 'after' === $position && 'preset1' === $preset ) {
2150 $class .= ' after-content';
2151 }
2152
2153 if ( 'preset3' === $preset ) {
2154 $html .= '<div class="rtsb-action-buttons top-part ' . esc_attr( $preset ) . '">';
2155 $html .= '<ul class="rtsb-action-button-list">';
2156
2157 ob_start();
2158 /**
2159 * Additional formatted action buttons hook.
2160 */
2161 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2162 $html .= ob_get_clean();
2163
2164 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2165 $html .= self::get_action_button_by_type( $items, 'compare' );
2166 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2167 $html .= '</ul>';
2168 $html .= '</div>';
2169 $html .= '<div class="rtsb-action-buttons bottom-part ' . esc_attr( $preset ) . '">';
2170 $html .= '<ul class="rtsb-action-button-list">';
2171 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2172 $html .= '</ul>';
2173 $html .= '</div>';
2174 } elseif ( 'preset1' === $preset || 'preset2' === $preset || 'preset4' === $preset ) {
2175 $html .= '<div class="rtsb-action-buttons ' . esc_attr( $class ) . '">';
2176 $html .= '<ul class="rtsb-action-button-list">';
2177 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
2178
2179 ob_start();
2180 /**
2181 * Additional formatted action buttons hook.
2182 */
2183 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2184 $html .= ob_get_clean();
2185
2186 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2187 $html .= self::get_action_button_by_type( $items, 'compare' );
2188 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2189 $html .= '</ul>';
2190 $html .= '</div>';
2191 }
2192
2193 return apply_filters( 'rtsb/elements/elementor/formatted_action_buttons', $html, $args );
2194 }
2195
2196 /**
2197 * Get Action Button HTML
2198 *
2199 * @param array $items Items.
2200 * @param string $type Button type.
2201 * @param string $cart_html Ajax cart HTML.
2202 * @param string $wrapper Wrapper tag.
2203 *
2204 * @return void|string
2205 */
2206 public static function get_action_button_by_type( $items, $type, $cart_html = '', $wrapper = 'li' ) {
2207 if ( ! in_array( $type, $items, true ) ) {
2208 return;
2209 }
2210
2211 $html = '';
2212 $class = 'rtsb-action-button-item';
2213
2214 if ( 'add_to_cart' === $type ) {
2215 $class .= ' rtsb-cart' . ( empty( $cart_html ) ? esc_attr( ' no-cart-button' ) : '' );
2216 } else {
2217 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2218 }
2219
2220 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2221 $html .= $cart_html;
2222 } else {
2223 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2224 }
2225
2226 if ( ! empty( $html ) ) {
2227 $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2228 }
2229
2230 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2231 }
2232
2233 /**
2234 * Social Share Platforms.
2235 *
2236 * @return mixed|null
2237 */
2238 public static function social_share_platforms_list() {
2239 return apply_filters(
2240 'rtsb/settings/social_share/platforms',
2241 [
2242 [
2243 'value' => 'facebook',
2244 'label' => esc_html__( 'Facebook', 'shopbuilder' ),
2245 ],
2246 [
2247 'value' => 'twitter',
2248 'label' => esc_html__( 'Twitter', 'shopbuilder' ),
2249 ],
2250 [
2251 'value' => 'linkedin',
2252 'label' => esc_html__( 'Linkedin', 'shopbuilder' ),
2253 ],
2254 [
2255 'value' => 'pinterest',
2256 'label' => esc_html__( 'Pinterest', 'shopbuilder' ),
2257 ],
2258 [
2259 'value' => 'skype',
2260 'label' => esc_html__( 'Skype', 'shopbuilder' ),
2261 ],
2262 [
2263 'value' => 'whatsapp',
2264 'label' => esc_html__( 'Whatsapp', 'shopbuilder' ),
2265 ],
2266 [
2267 'value' => 'reddit',
2268 'label' => esc_html__( 'Reddit', 'shopbuilder' ),
2269 ],
2270 [
2271 'value' => 'telegram',
2272 'label' => esc_html__( 'Telegram', 'shopbuilder' ),
2273 ],
2274 ]
2275 );
2276 }
2277
2278 /**
2279 * Get Social Share link HTML.
2280 *
2281 * @param int $id Post ID.
2282 * @param array $types Preset type.
2283 * @param string $preset Style type.
2284 * @param boolean $show_icon Show icon.
2285 * @param boolean $show_text Show text.
2286 *
2287 * @return string
2288 */
2289 public static function get_social_share_html( int $id, array $types, string $preset = 'default', $show_icon = true, $show_text = true ) {
2290 $attr = [ 'postid' => $id ];
2291 $output = '';
2292
2293 if ( empty( $types ) ) {
2294 return $output;
2295 }
2296
2297 foreach ( $types as $type ) {
2298 $link = [];
2299 $link['type'] = $type['share_items'];
2300 $link['class'] = '';
2301 $link['img'] = apply_filters( 'rtsb/elements/share/default_img', '', $id, $link );
2302
2303 if ( 'site' === $id ) {
2304 $link['url'] = home_url();
2305 $link['title'] = wp_strip_all_tags( get_bloginfo( 'name' ) );
2306 } elseif ( 0 === strpos( $id, 'http' ) ) {
2307 $link['url'] = $id;
2308 $link['title'] = '';
2309 } else {
2310 $link['url'] = get_permalink( $id );
2311 $link['title'] = wp_strip_all_tags( get_the_title( $id ) );
2312
2313 if ( has_post_thumbnail( $id ) ) {
2314 $link['img'] = wp_get_attachment_image_url( get_post_thumbnail_id( $id ), 'full' );
2315 }
2316
2317 $link['img'] = apply_filters( 'rtsb/elements/share/single_img', $link['img'], $id, $link );
2318 }
2319
2320 $link['url'] = apply_filters( 'rtsb/elements/share/url', $link['url'], $link );
2321
2322 switch ( $type['share_items'] ) {
2323 case 'facebook':
2324 $link['link'] = esc_url( 'https://www.facebook.com/sharer/sharer.php?u=' . $link['url'] . '&display=popup&ref=plugin&src=share_button' );
2325 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="18.8125" height="32" viewBox="0 0 602 1024"><path d="M548 6.857v150.857h-89.714q-49.143 0-66.286 20.571t-17.143 61.714v108h167.429l-22.286 169.143h-145.143v433.714h-174.857v-433.714h-145.714v-169.143h145.714v-124.571q0-106.286 59.429-164.857t158.286-58.571q84 0 130.286 6.857z"></path></svg>';
2326 $link['attr_title'] = esc_html__( 'Share on Facebook', 'shopbuilder' );
2327 $link['social_network'] = 'Facebook';
2328 $link['social_action'] = 'Share';
2329 break;
2330 case 'twitter':
2331 $link['link'] = esc_url( 'https://x.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
2332 $link['icon'] = '<svg width="24" height="24" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" /></svg>';
2333 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2334 $link['social_network'] = 'Twitter';
2335 $link['social_action'] = 'Tweet';
2336 break;
2337 case 'pinterest':
2338 $link['link'] = esc_url( 'https://pinterest.com/pin/create/button/?url=' . $link['url'] . '&media=' . $link['img'] . '&description=' . $link['title'] );
2339 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="22.84375" height="32" viewBox="0 0 731 1024"><path d="M0 341.143q0-61.714 21.429-116.286t59.143-95.143 86.857-70.286 105.714-44.571 115.429-14.857q90.286 0 168 38t126.286 110.571 48.571 164q0 54.857-10.857 107.429t-34.286 101.143-57.143 85.429-82.857 58.857-108 22q-38.857 0-77.143-18.286t-54.857-50.286q-5.714 22.286-16 64.286t-13.429 54.286-11.714 40.571-14.857 40.571-18.286 35.714-26.286 44.286-35.429 49.429l-8 2.857-5.143-5.714q-8.571-89.714-8.571-107.429 0-52.571 12.286-118t38-164.286 29.714-116q-18.286-37.143-18.286-96.571 0-47.429 29.714-89.143t75.429-41.714q34.857 0 54.286 23.143t19.429 58.571q0 37.714-25.143 109.143t-25.143 106.857q0 36 25.714 59.714t62.286 23.714q31.429 0 58.286-14.286t44.857-38.857 32-54.286 21.714-63.143 11.429-63.429 3.714-56.857q0-98.857-62.571-154t-163.143-55.143q-114.286 0-190.857 74t-76.571 187.714q0 25.143 7.143 48.571t15.429 37.143 15.429 26 7.143 17.429q0 16-8.571 41.714t-21.143 25.714q-1.143 0-9.714-1.714-29.143-8.571-51.714-32t-34.857-54-18.571-61.714-6.286-60.857z"></path></svg>';
2340 $link['attr_title'] = esc_html__( 'Share on Pinterest', 'shopbuilder' );
2341 $link['social_network'] = 'Pinterest';
2342 $link['social_action'] = 'Pin';
2343 break;
2344 case 'linkedin':
2345 $link['link'] = esc_url( 'https://www.linkedin.com/shareArticle?url=' . $link['url'] . '&title=' . $link['title'] );
2346 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="27.4375" height="32" viewBox="0 0 878 1024"><path d="M199.429 357.143v566.286h-188.571v-566.286h188.571zM211.429 182.286q0.571 41.714-28.857 69.714t-77.429 28h-1.143q-46.857 0-75.429-28t-28.571-69.714q0-42.286 29.429-70t76.857-27.714 76 27.714 29.143 70zM877.714 598.857v324.571h-188v-302.857q0-60-23.143-94t-72.286-34q-36 0-60.286 19.714t-36.286 48.857q-6.286 17.143-6.286 46.286v316h-188q1.143-228 1.143-369.714t-0.571-169.143l-0.571-27.429h188v82.286h-1.143q11.429-18.286 23.429-32t32.286-29.714 49.714-24.857 65.429-8.857q97.714 0 157.143 64.857t59.429 190z"></path></svg>';
2347 $link['attr_title'] = esc_html__( 'Share on LinkedIn', 'shopbuilder' );
2348 $link['social_network'] = 'LinkedIn';
2349 $link['social_action'] = 'Share';
2350 break;
2351 case 'skype':
2352 $link['link'] = esc_url( 'https://web.skype.com/share?url=' . $link['url'] );
2353 $link['icon'] = '<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve" class="eapps-social-share-buttons-item-icon"> <path d="M23.016,13.971c0.111-0.638,0.173-1.293,0.173-1.963 c0-6.213-5.014-11.249-11.199-11.249c-0.704,0-1.393,0.068-2.061,0.193C8.939,0.348,7.779,0,6.536,0C2.926,0,0,2.939,0,6.565 c0,1.264,0.357,2.443,0.973,3.445c-0.116,0.649-0.18,1.316-0.18,1.999c0,6.212,5.014,11.25,11.198,11.25 c0.719,0,1.419-0.071,2.099-0.201C15.075,23.656,16.229,24,17.465,24C21.074,24,24,21.061,24,17.435 C24,16.163,23.639,14.976,23.016,13.971z M12.386,19.88c-3.19,0-6.395-1.453-6.378-3.953c0.005-0.754,0.565-1.446,1.312-1.446 c1.877,0,1.86,2.803,4.85,2.803c2.098,0,2.814-1.15,2.814-1.95c0-2.894-9.068-1.12-9.068-6.563c0-2.945,2.409-4.977,6.196-4.753 c3.61,0.213,5.727,1.808,5.932,3.299c0.102,0.973-0.543,1.731-1.662,1.731c-1.633,0-1.8-2.188-4.613-2.188 c-1.269,0-2.341,0.53-2.341,1.679c0,2.402,9.014,1.008,9.014,6.295C18.441,17.882,16.012,19.88,12.386,19.88z"></path> </svg>';
2354 $link['attr_title'] = esc_html__( 'Share on Skype', 'shopbuilder' );
2355 $link['social_network'] = 'Skype';
2356 $link['social_action'] = 'Skype';
2357 break;
2358 case 'whatsapp':
2359 $link['link'] = esc_url( 'https://wa.me/?text=' . $link['title'] . ' ' . $link['url'] );
2360 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-whatsapp" viewBox="0 0 16 16"> <path d="M13.601 2.326A7.854 7.854 0 0 0 7.994 0C3.627 0 .068 3.558.064 7.926c0 1.399.366 2.76 1.057 3.965L0 16l4.204-1.102a7.933 7.933 0 0 0 3.79.965h.004c4.368 0 7.926-3.558 7.93-7.93A7.898 7.898 0 0 0 13.6 2.326zM7.994 14.521a6.573 6.573 0 0 1-3.356-.92l-.24-.144-2.494.654.666-2.433-.156-.251a6.56 6.56 0 0 1-1.007-3.505c0-3.626 2.957-6.584 6.591-6.584a6.56 6.56 0 0 1 4.66 1.931 6.557 6.557 0 0 1 1.928 4.66c-.004 3.639-2.961 6.592-6.592 6.592zm3.615-4.934c-.197-.099-1.17-.578-1.353-.646-.182-.065-.315-.099-.445.099-.133.197-.513.646-.627.775-.114.133-.232.148-.43.05-.197-.1-.836-.308-1.592-.985-.59-.525-.985-1.175-1.103-1.372-.114-.198-.011-.304.088-.403.087-.088.197-.232.296-.346.1-.114.133-.198.198-.33.065-.134.034-.248-.015-.347-.05-.099-.445-1.076-.612-1.47-.16-.389-.323-.335-.445-.34-.114-.007-.247-.007-.38-.007a.729.729 0 0 0-.529.247c-.182.198-.691.677-.691 1.654 0 .977.71 1.916.81 2.049.098.133 1.394 2.132 3.383 2.992.47.205.84.326 1.129.418.475.152.904.129 1.246.08.38-.058 1.171-.48 1.338-.943.164-.464.164-.86.114-.943-.049-.084-.182-.133-.38-.232z"/> </svg>';
2361 $link['attr_title'] = esc_html__( 'Share on Whatsapp', 'shopbuilder' );
2362 $link['social_network'] = 'Whatsapp';
2363 $link['social_action'] = 'Share';
2364 break;
2365 case 'reddit':
2366 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2367 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path d="M324,256a36,36,0,1,0,36,36A36,36,0,0,0,324,256Z"/><circle cx="188" cy="292" r="36" transform="translate(-97.43 94.17) rotate(-22.5)"/><path d="M496,253.77c0-31.19-25.14-56.56-56-56.56a55.72,55.72,0,0,0-35.61,12.86c-35-23.77-80.78-38.32-129.65-41.27l22-79L363.15,103c1.9,26.48,24,47.49,50.65,47.49,28,0,50.78-23,50.78-51.21S441,48,413,48c-19.53,0-36.31,11.19-44.85,28.77l-90-17.89L247.05,168.4l-4.63.13c-50.63,2.21-98.34,16.93-134.77,41.53A55.38,55.38,0,0,0,72,197.21c-30.89,0-56,25.37-56,56.56a56.43,56.43,0,0,0,28.11,49.06,98.65,98.65,0,0,0-.89,13.34c.11,39.74,22.49,77,63,105C146.36,448.77,199.51,464,256,464s109.76-15.23,149.83-42.89c40.53-28,62.85-65.27,62.85-105.06a109.32,109.32,0,0,0-.84-13.3A56.32,56.32,0,0,0,496,253.77ZM414,75a24,24,0,1,1-24,24A24,24,0,0,1,414,75ZM42.72,253.77a29.6,29.6,0,0,1,29.42-29.71,29,29,0,0,1,13.62,3.43c-15.5,14.41-26.93,30.41-34.07,47.68A30.23,30.23,0,0,1,42.72,253.77ZM390.82,399c-35.74,24.59-83.6,38.14-134.77,38.14S157,423.61,121.29,399c-33-22.79-51.24-52.26-51.24-83A78.5,78.5,0,0,1,75,288.72c5.68-15.74,16.16-30.48,31.15-43.79a155.17,155.17,0,0,1,14.76-11.53l.3-.21,0,0,.24-.17c35.72-24.52,83.52-38,134.61-38s98.9,13.51,134.62,38l.23.17.34.25A156.57,156.57,0,0,1,406,244.92c15,13.32,25.48,28.05,31.16,43.81a85.44,85.44,0,0,1,4.31,17.67,77.29,77.29,0,0,1,.6,9.65C442.06,346.77,423.86,376.24,390.82,399Zm69.6-123.92c-7.13-17.28-18.56-33.29-34.07-47.72A29.09,29.09,0,0,1,440,224a29.59,29.59,0,0,1,29.41,29.71A30.07,30.07,0,0,1,460.42,275.1Z"/><path d="M323.23,362.22c-.25.25-25.56,26.07-67.15,26.27-42-.2-66.28-25.23-67.31-26.27h0a4.14,4.14,0,0,0-5.83,0l-13.7,13.47a4.15,4.15,0,0,0,0,5.89h0c3.4,3.4,34.7,34.23,86.78,34.45,51.94-.22,83.38-31.05,86.78-34.45h0a4.16,4.16,0,0,0,0-5.9l-13.71-13.47a4.13,4.13,0,0,0-5.81,0Z"/></svg>';
2368 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2369 $link['social_network'] = 'Reddit';
2370 $link['social_action'] = 'Share';
2371 break;
2372 case 'telegram':
2373 $link['link'] = esc_url( 'https://telegram.me/share/url?text=' . $link['title'] . '&url=' . $link['url'] );
2374 $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telegram" viewBox="0 0 16 16"> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133.958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.05-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.817-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.088.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.236.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.763.166-2.984 1.09z"/> </svg>';
2375 $link['attr_title'] = esc_html__( 'Share on Telegram', 'shopbuilder' );
2376 $link['social_network'] = 'Telegram';
2377 $link['social_action'] = 'Share';
2378 break;
2379 }
2380
2381 $link['label'] = $type['share_text'];
2382 $link['target'] = '_blank';
2383 $link['rel'] = 'nofollow noopener noreferrer';
2384
2385 $data = '';
2386 $link = apply_filters( 'rtsb/elements/share/share_link', $link, $id, $preset );
2387 $icon = apply_filters( 'rtsb/elements/share/share_icon', $link['icon'], $preset );
2388 $target = ! empty( $link['target'] ) ? ' target="' . esc_attr( $link['target'] ) . '" ' : '';
2389 $rel = ! empty( $link['rel'] ) ? ' rel="' . esc_attr( $link['rel'] ) . '" ' : '';
2390 $attr_title = ! empty( $link['attr_title'] ) ? ' title="' . esc_attr( $link['attr_title'] ) . '" ' : '';
2391 $elements = [];
2392
2393 // Add classes.
2394 $css_classes = [
2395 'rtsb-share-btn',
2396 sanitize_html_class( $link['type'] ),
2397 ];
2398 $css_classes = array_merge( $css_classes, explode( ' ', $link['class'] ) );
2399 $css_classes = array_map( 'sanitize_html_class', $css_classes );
2400 $css_classes = implode( ' ', array_filter( $css_classes ) );
2401
2402 unset( $attr['pin-do'], $attr['action'] );
2403
2404 if ( 'pinterest' === $type['share_items'] ) {
2405 $attr['pin-do'] = 'none';
2406 }
2407
2408 if ( 'whatsapp' === $type['share_items'] ) {
2409 $attr['action'] = 'share/whatsapp/share';
2410 }
2411
2412 $attr = apply_filters( 'rtsb/elements/share/link_data', $attr, $link, $id );
2413
2414 if ( ! empty( $attr ) ) {
2415 foreach ( $attr as $key => $val ) {
2416 $data .= ' data-' . sanitize_html_class( $key ) . '="' . esc_attr( $val ) . '"';
2417 }
2418 }
2419
2420 $additional_attr = apply_filters( 'rtsb/elements/share/additional_attr', [], $link, $id, $preset );
2421
2422 if ( ! empty( $additional_attr ) ) {
2423 $attr_output = join( ' ', $additional_attr );
2424
2425 if ( ! empty( $data ) ) {
2426 $attr_output = ' ' . $attr_output;
2427 }
2428
2429 $data .= $attr_output;
2430 }
2431
2432 $elements['wrapper_start'] = sprintf(
2433 '<li class="rtsb-share-item"><a href="%s"%s%s%s class="%s"%s>',
2434 ! empty( $link['link'] ) ? esc_attr( $link['link'] ) : '',
2435 $attr_title,
2436 $target,
2437 $rel,
2438 $css_classes,
2439 $data
2440 );
2441 $elements['wrapper_end'] = '</a></li>';
2442
2443 $elements['icon'] = $show_icon ? '<span class="rtsb-share-icon">' . ( ! empty( $icon ) ? $icon : null ) . '</span>' : null;
2444 $elements['label'] = $show_text && ! empty( $link['label'] ) ? '<span class="rtsb-share-label">' . $link['label'] . '</span>' : null;
2445 $elements['icon_label'] = '<span class="rtsb-share-icon-label">' . $elements['icon'] . $elements['label'] . '</span>';
2446 $elements = apply_filters( 'rtsb/elements/share/output_elements', $elements, $link, $id );
2447
2448 $output .= $elements['wrapper_start'] . $elements['icon_label'] . $elements['wrapper_end'];
2449 }
2450
2451 return apply_filters( 'rtsb/elements/share/list_output', $output );
2452 }
2453
2454 /**
2455 * Social Share Platforms.
2456 *
2457 * @param string $module Module.
2458 *
2459 * @return true|void
2460 */
2461 public static function is_module_active( $module ) {
2462 $modulelist = self::get_modules_list();
2463
2464 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2465 return true;
2466 }
2467 }
2468
2469 /**
2470 * Elementor Widget Active.
2471 *
2472 * @param string $widget Elementor Widget.
2473 *
2474 * @return true|void
2475 */
2476 public static function is_elementor_widget_active( $widget ) {
2477 $element_list = self::get_widgets_list();
2478
2479 if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2480 return true;
2481 }
2482 }
2483
2484 /***
2485 * Save Settings data
2486 *
2487 * @param string $section_id Section ID.
2488 * @param string $block_id Block ID.
2489 * @param array $rawOptions Raw Options.
2490 *
2491 * @return array
2492 */
2493 public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2494 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2495 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2496 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2497 $results = [
2498 'status' => true,
2499 'message' => '',
2500 ];
2501 if ( ! $section_id || ! $block_id ) {
2502 $results['status'] = false;
2503 $results['message'] = esc_html__( 'Section , block or options may be empty', 'shopbuilder' );
2504
2505 return $results;
2506 }
2507 $sections = Settings::instance()->get_sections();
2508 if ( empty( $sections[ $section_id ] ) || empty( $sections[ $section_id ]['list'][ $block_id ] ) ) {
2509 $results['status'] = false;
2510 $results['message'] = esc_html__( 'No section or block found with given data', 'shopbuilder' );
2511
2512 return $results;
2513 }
2514
2515 $options = DataModel::source()->get_option( $section_id, [], false );
2516 $changed = false;
2517 $fields = [];
2518 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2519 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2520 }
2521 do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2522 if ( empty( $fields ) ) {
2523 if ( isset( $rawOptions['active'] ) ) {
2524 $changed = true;
2525 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2526 }
2527 } else {
2528 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2529 if ( 'active' === $raw_option_key ) {
2530 $changed = true;
2531 $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2532 } else {
2533 if ( isset( $fields[ $raw_option_key ] ) ) {
2534 $field = $fields[ $raw_option_key ];
2535 if ( 'switch' === $field['type'] ) {
2536 $value = 'on' === $raw_value ? 'on' : '';
2537 } elseif ( 'repeaters' === $field['type'] ) {
2538 $the_value = [];
2539 $rep_title = [];
2540 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2541 foreach ( $raw_value as $key => $value ) {
2542 if ( is_string( $value ) ) {
2543 $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2544 } else {
2545 $the_value_decoded = $value;
2546 }
2547 $cr = $the_value_decoded->title ?? '';
2548 if ( in_array( $cr, $rep_title, true ) ) {
2549 continue;
2550 }
2551 $rep_title[] = $cr;
2552 $the_value[] = $the_value_decoded;
2553 }
2554 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2555 $the_value = $raw_value;
2556 }
2557 $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2558 } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2559 $manual_field_value = [];
2560 if ( is_array( $raw_value ) ) {
2561 foreach ( $raw_value as $key => $value ) {
2562 $manual_field_value[] = json_decode( stripslashes( $value ), true );
2563 }
2564 $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2565 } elseif ( is_string( $raw_value ) ) {
2566 $value = $raw_value;
2567 }
2568 } else {
2569 if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2570 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2571 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2572 $value = array_map( $field['sanitize_fn'], $raw_value );
2573 } else {
2574 $value = array_map( 'sanitize_text_field', $raw_value );
2575 }
2576 } else {
2577 $value = [];
2578 }
2579 } else {
2580 if ( ! empty( $field['sanitize_fn'] ) ) {
2581 if ( is_callable( $field['sanitize_fn'] ) ) {
2582 $value = $field['sanitize_fn']( $raw_value );
2583 } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2584 $value = $raw_value;
2585 } else {
2586 $value = $field['sanitize_fn']( $raw_value );
2587 }
2588 } else {
2589 $value = sanitize_text_field( $raw_value );
2590 }
2591 }
2592 }
2593 $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2594 $changed = true;
2595 }
2596 }
2597 }
2598 }
2599 if ( ! $changed ) {
2600 $results['status'] = false;
2601 $results['message'] = esc_html__( 'No changes found for update', 'shopbuilder' );
2602
2603 return $results;
2604 }
2605
2606 DataModel::source()->set_option( $section_id, $options );
2607
2608 $results['status'] = true;
2609 $results['message'] = esc_html__( 'Successfully Saved', 'shopbuilder' );
2610 $results['sections'] = $sections;
2611
2612 return $results;
2613 }
2614
2615 /**
2616 * Count products by taxonomies.
2617 *
2618 * @param array $terms Terms.
2619 * @param string $taxonomy Taxonomy.
2620 *
2621 * @return int|void
2622 */
2623 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2624 if ( empty( $terms ) || ! is_array( $terms ) ) {
2625 return;
2626 }
2627
2628 $args = [
2629 'limit' => -1,
2630 'return' => 'ids',
2631 ];
2632
2633 if ( 'product_cat' === $taxonomy ) {
2634 $args['product_category_id'] = $terms;
2635 } elseif ( 'product_brand' === $taxonomy ) {
2636 $args['product_brand_id'] = $terms;
2637 } else {
2638 $args['product_tag_id'] = $terms;
2639 }
2640
2641 $query = new WC_Product_Query( $args );
2642
2643 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2644 }
2645
2646 /**
2647 * Count products by attribute terms.
2648 *
2649 * @param array $term_ids Term IDs.
2650 * @param string $relation Relation.
2651 *
2652 * @return int
2653 */
2654 public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2655 if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2656 return 0;
2657 }
2658
2659 $terms_by_taxonomy = [];
2660
2661 foreach ( $term_ids as $term_id ) {
2662 $term = get_term( $term_id );
2663 if ( ! is_wp_error( $term ) && $term ) {
2664 if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2665 $terms_by_taxonomy[ $term->taxonomy ] = [];
2666 }
2667 $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2668 }
2669 }
2670
2671 if ( empty( $terms_by_taxonomy ) ) {
2672 return 0;
2673 }
2674
2675 $args = [
2676 'status' => 'publish',
2677 'limit' => -1,
2678 'return' => 'ids',
2679 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2680 'relation' => strtoupper( $relation ),
2681 ],
2682 ];
2683
2684 foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2685 $args['tax_query'][] = [
2686 'taxonomy' => $taxonomy,
2687 'field' => 'term_id',
2688 'terms' => $terms,
2689 'operator' => 'IN',
2690 ];
2691 }
2692
2693 $query = new WC_Product_Query( $args );
2694 $products = $query->get_products();
2695
2696 return count( $products );
2697 }
2698
2699 /**
2700 * Check if product filters widget has ajax.
2701 *
2702 * @param string $page Page name.
2703 *
2704 * @return bool
2705 */
2706 public static function product_filters_has_ajax( $page ) {
2707 if ( empty( $page ) ) {
2708 return false;
2709 }
2710
2711 if ( 'page' === get_post_type() ) {
2712 return false;
2713 }
2714
2715 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2716 if ( ! $id ) {
2717 return false;
2718 }
2719 $cache_key = 'product_filters_has_ajax_' . $id;
2720 if ( isset( self::$cache[ $cache_key ] ) ) {
2721 return self::$cache[ $cache_key ];
2722 }
2723 $elmap = ElementorDataMap::instance();
2724 $ajax = [];
2725
2726 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2727 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2728 }
2729
2730 self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2731 return ! empty( $ajax[0] );
2732 }
2733
2734 /**
2735 * Check if product has applied filter.
2736 *
2737 * @param string $page Page name.
2738 *
2739 * @return bool
2740 */
2741 public static function product_has_applied_filters( $page ) {
2742 if ( empty( $page ) ) {
2743 return false;
2744 }
2745
2746 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2747 $elmap = ElementorDataMap::instance();
2748 $ajax = [];
2749 if ( ! $id ) {
2750 return false;
2751 }
2752 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2753 $ajax[] = ! isset( $data['settings']['active_filter'] );
2754
2755 }
2756
2757 return ! empty( $ajax[0] );
2758 }
2759
2760 /**
2761 * Get WooCommerce product categories.
2762 *
2763 * @param string|null $search_query The search string for category names.
2764 *
2765 * @return array An array of product categories with 'value' and 'label' keys.
2766 */
2767 public static function products_category_query( $search_query = null ) {
2768 if ( ! is_admin() ) {
2769 return [];
2770 }
2771
2772 $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2773
2774 if ( isset( self::$cache[ $cache_key ] ) ) {
2775 return self::$cache[ $cache_key ];
2776 }
2777 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2778 if ( ! $results ) {
2779 global $wpdb;
2780 $sql = "SELECT t.term_id, t.name
2781 FROM {$wpdb->terms} t
2782 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2783 WHERE tt.taxonomy = 'product_cat'";
2784
2785 if ( $search_query ) {
2786 $sql .= ' AND t.name LIKE %s';
2787 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2788 } else {
2789 $prepared_sql = $sql; // No need for placeholders.
2790 }
2791
2792 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2793 // Cache the results in the object cache for future use.
2794 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2795 Cache::set_data_cache_key( $cache_key );
2796 }
2797
2798 $cats = [];
2799 if ( ! is_array( $results ) && ! count( $results ) ) {
2800 return $cats;
2801 }
2802 foreach ( $results as $row ) {
2803 $category_id = $row->term_id;
2804 $category_title = $row->name;
2805
2806 $cats[] = [
2807 'value' => $category_id,
2808 'label' => $category_title,
2809 ];
2810 }
2811
2812 // Cache the results in a static variable for the next call.
2813 self::$cache[ $cache_key ] = $cats;
2814 return $cats;
2815 }
2816 /**
2817 * Get WooCommerce product categories.
2818 *
2819 * @param string|null $search_query The search string for category names.
2820 *
2821 * @return array An array of product categories with 'value' and 'label' keys.
2822 */
2823 public static function products_tags_query( $search_query = null ) {
2824 if ( ! is_admin() ) {
2825 return [];
2826 }
2827
2828 $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2829
2830 if ( isset( self::$cache[ $cache_key ] ) ) {
2831 return self::$cache[ $cache_key ];
2832 }
2833 $results = wp_cache_get( $cache_key, 'shopbuilder' );
2834 if ( ! $results ) {
2835 global $wpdb;
2836 $sql = "SELECT t.term_id, t.name
2837 FROM {$wpdb->terms} t
2838 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2839 WHERE tt.taxonomy = 'product_tag'";
2840
2841 if ( $search_query ) {
2842 $sql .= ' AND t.name LIKE %s';
2843 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2844 } else {
2845 $prepared_sql = $sql; // No need for placeholders.
2846 }
2847
2848 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2849 // Cache the results in the object cache for future use.
2850 wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2851 Cache::set_data_cache_key( $cache_key );
2852 }
2853
2854 $cats = [];
2855 if ( ! is_array( $results ) && ! count( $results ) ) {
2856 return $cats;
2857 }
2858 foreach ( $results as $row ) {
2859 $category_id = $row->term_id;
2860 $category_title = $row->name;
2861
2862 $cats[] = [
2863 'value' => $category_id,
2864 'label' => $category_title,
2865 ];
2866 }
2867
2868 // Cache the results in a static variable for the next call.
2869 self::$cache[ $cache_key ] = $cats;
2870 return $cats;
2871 }
2872
2873 /**
2874 * @param string $search_query Search query.
2875 *
2876 * @return array
2877 */
2878 public static function get_registered_post_types( $search_query = null ) {
2879 // Get all registered post types.
2880 $registered_post_types = get_post_types(
2881 [
2882 'public' => true,
2883 '_builtin' => false,
2884 ],
2885 'objects'
2886 );
2887
2888 unset( $registered_post_types['elementor_library'] );
2889 unset( $registered_post_types['e-landing-page'] );
2890 unset( $registered_post_types['rtsb_builder'] );
2891
2892 $post_types = [];
2893
2894 // Check if a search query is provided.
2895 if ( ! empty( $search_query ) ) {
2896 // Filter post types based on the search query.
2897 $registered_post_types = array_filter(
2898 $registered_post_types,
2899 function ( $post_type ) use ( $search_query ) {
2900 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2901 }
2902 );
2903 // Check if 'post' match the search query and include them if they do.
2904 if ( stripos( 'post', $search_query ) !== false ) {
2905 $post_types[] = [
2906 'value' => 'post',
2907 'label' => 'Post',
2908 ];
2909 }
2910 } else {
2911 $post_types[] = [
2912 'value' => 'post',
2913 'label' => 'Post',
2914 ];
2915 }
2916
2917 // Convert the filtered post types to an array.
2918 foreach ( $registered_post_types as $post_type ) {
2919 $post_types[] = [
2920 'value' => $post_type->name,
2921 'label' => $post_type->labels->singular_name,
2922 ];
2923 }
2924
2925 return $post_types;
2926 }
2927
2928 /**
2929 * Get Post Types.
2930 *
2931 * @param string $search_query Search query.
2932 * @param array $args Arguments.
2933 *
2934 * @return array
2935 */
2936 public static function get_post_types( $search_query = null, $args = [] ) {
2937
2938 $args = wp_parse_args(
2939 $args,
2940 [
2941 'post_type' => 'any',
2942 'posts_per_page' => 20,
2943 'orderby' => 'ID',
2944 'order' => 'DESC',
2945 ]
2946 );
2947
2948 if ( 'archive' !== $args['post_type'] ) {
2949 if ( $search_query ) {
2950 $args['s'] = $search_query;
2951 }
2952 $posts = [];
2953 $query_results = get_posts( $args );
2954 foreach ( $query_results as $post ) {
2955 $posts[] = [
2956 'value' => $post->ID,
2957 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2958 ];
2959 }
2960
2961 if ( $search_query ) {
2962 if ( strpos( '-error 404', $search_query ) ) {
2963 $posts[] = [
2964 'value' => 'error',
2965 'label' => __( '404 Error', 'shopbuilder' ),
2966 ];
2967 }
2968 } else {
2969 if ( 'page' === $args['post_type'] ) {
2970 $posts[] = [
2971 'value' => 'error',
2972 'label' => __( '404 Error', 'shopbuilder' ),
2973 ];
2974 }
2975 }
2976 } else {
2977 $posts = self::get_registered_post_types( $search_query );
2978 }
2979
2980 return $posts;
2981 }
2982
2983 /**
2984 * Get all Elementor breakpoints.
2985 *
2986 * @return array
2987 */
2988 public static function get_elementor_breakpoints() {
2989 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
2990 }
2991
2992 /**
2993 * Render view.
2994 *
2995 * @param string $viewName View name.
2996 * @param array $args View args.
2997 * @param boolean $return View return.
2998 * @return string|void
2999 */
3000 public static function renderView( $viewName, $args = [], $return = false ) {
3001 $viewName = str_replace( '.', '/', $viewName );
3002
3003 if ( ! empty( $args ) && is_array( $args ) ) {
3004 extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
3005 }
3006
3007 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
3008
3009 if ( ! file_exists( $view_file ) ) {
3010 _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
3011
3012 return;
3013 }
3014
3015 if ( $return ) {
3016 ob_start();
3017 include $view_file;
3018
3019 return ob_get_clean();
3020 } else {
3021 include $view_file;
3022 }
3023 }
3024
3025 /**
3026 * Best selling product query.
3027 *
3028 * @param int $minimum_sale Minimum sale.
3029 * @param int $limit Total limit.
3030 *
3031 * @return array|mixed
3032 */
3033 public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3034 $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
3035 // Check if the data is in the cache.
3036 if ( isset( self::$cache[ $cache_key ] ) ) {
3037 return self::$cache[ $cache_key ];
3038 }
3039 $cached_data = get_transient( $cache_key );
3040 if ( $cached_data ) {
3041 self::$cache[ $cache_key ] = $cached_data;
3042 return $cached_data;
3043 }
3044 global $wpdb;
3045 $sql = $wpdb->prepare(
3046 "
3047 SELECT ID
3048 FROM {$wpdb->posts} AS p
3049 LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3050 WHERE p.post_type = 'product'
3051 AND p.post_status = 'publish'
3052 AND pm.meta_key = 'total_sales'
3053 AND pm.meta_value >= %d
3054 ORDER BY pm.meta_value + 0 DESC
3055 LIMIT %d
3056 ",
3057 $minimum_sale,
3058 $limit
3059 );
3060 $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
3061 // Cache the result for future use.
3062 set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
3063 self::$cache[ $cache_key ] = $best_selling;
3064 return $best_selling;
3065 }
3066
3067 /**
3068 * @param null|Object $product Product Object.
3069 * @param int $days_threshold Date String.
3070 *
3071 * @return bool
3072 */
3073 public static function is_new_product( $product = null, $days_threshold = 30 ) {
3074 if ( is_null( $product ) ) {
3075 global $product;
3076 }
3077 if ( ! $product instanceof WC_Product ) {
3078 return false;
3079 }
3080
3081 $product_id = $product->get_id();
3082 $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
3083 $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
3084
3085 if ( isset( self::$cache[ $cache_key ] ) ) {
3086 return self::$cache[ $cache_key ];
3087 }
3088 $date_created = $product->get_date_created();
3089 if ( ! $date_created ) {
3090 return false;
3091 }
3092 $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
3093
3094 // Calculate the difference in days.
3095 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
3096
3097 // Check if the product is considered new.
3098 $is_new = $days_difference <= $days_threshold;
3099
3100 // Cache the result for a day.
3101 self::$cache[ $cache_key ] = $is_new;
3102
3103 return $is_new;
3104 }
3105
3106 /**
3107 * Checks if a feature is disabled for guest users based on the provided option.
3108 *
3109 * @param string $option The option to retrieve from the item.
3110 * @param mixed $default The default value if the option is not found.
3111 *
3112 * @return bool
3113 */
3114 public static function is_guest_feature_disabled( $option, $default ) {
3115 $disabled = self::get_option( 'general', 'guest_user', $option, $default );
3116
3117 return ! is_user_logged_in() && $disabled;
3118 }
3119
3120 /**
3121 * Checks if a feature is disabled for guest users based on the provided option.
3122 *
3123 * @return void
3124 */
3125 public static function woocommerce_output_all_notices() {
3126 if ( function_exists( 'wc_print_notices' ) ) :
3127 echo '<div class="rtsb-notice">';
3128 woocommerce_output_all_notices();
3129 echo '</div>';
3130 endif;
3131 }
3132
3133 /**
3134 * @param object $product Product.
3135 * @return bool
3136 */
3137 public static function is_visible_qty_input( $product = null ) {
3138 $is_visible_qty = true;
3139 if ( $product instanceof \WC_Product ) {
3140 if ( $product->is_sold_individually() ) {
3141 $is_visible_qty = false;
3142 } elseif ( $product->managing_stock() ) {
3143 if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3144 $is_visible_qty = true;
3145 } elseif ( $product->get_stock_quantity() < 2 ) {
3146 $is_visible_qty = false;
3147 }
3148 }
3149 }
3150 return $is_visible_qty;
3151 }
3152
3153 /**
3154 * @param int $object_id Object id.
3155 * @param string $element_type element type.
3156 * @param string|null $current_lang null for current language, default for default languages.
3157 * @return false|mixed|null
3158 */
3159 public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3160 if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3161 return $object_id;
3162 }
3163 if ( ! $object_id || ! $element_type ) {
3164 return $object_id;
3165 }
3166 if ( 'default' === $current_lang ) {
3167 $lang = apply_filters( 'wpml_default_language', null );
3168 } else {
3169 $lang = null;
3170 }
3171 return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3172 }
3173
3174 /**
3175 * @return string
3176 */
3177 public static function wpml_current_language() {
3178 $current = apply_filters( 'wpml_current_language', null );
3179 $default = apply_filters( 'wpml_default_language', null );
3180 return $default !== $current ? $current : null;
3181 }
3182
3183 /**
3184 * Custom icons.
3185 *
3186 * @return string[]
3187 */
3188 public static function get_custom_icon_names() {
3189 return [
3190 'heart-empty',
3191 'heart',
3192 'eye',
3193 'exchange',
3194 'plus',
3195 'minus',
3196 'avatar',
3197 'pay',
3198 'share',
3199 'clock',
3200 'check-alt',
3201 'check',
3202 'delete',
3203 'marker',
3204 'list',
3205 'list-2',
3206 'power',
3207 'cart',
3208 'cart-2',
3209 'cart-3',
3210 'downloads',
3211 'zoom',
3212 'user-edit',
3213 'grid',
3214 'filter',
3215 'billing',
3216 'login',
3217 'payment',
3218 'search',
3219 'edit',
3220 'coupon',
3221 'arrows-cw',
3222 'trash-empty',
3223 ];
3224 }
3225
3226 /**
3227 * Retrieve an array of custom icons with their HTML representation.
3228 *
3229 * @return array
3230 */
3231 public static function get_icons() {
3232 $icon = self::get_custom_icon_names();
3233 $icons_array = [];
3234 foreach ( $icon as $value ) {
3235 $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3236 }
3237 return $icons_array;
3238 }
3239
3240 /**
3241 * Generates HTML markup for displaying an endpoint icon.
3242 *
3243 * @param array $data The endpoint data containing icon information.
3244 * @param boolean $wrapper Whether to wrap the icon in a span element.
3245 *
3246 * @return string
3247 */
3248 public static function get_icon_html( $data, $wrapper = true ) {
3249 if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3250 return '';
3251 }
3252 $endpoint = '';
3253 $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3254
3255 if ( 'select_icon' === $data['icon_source'] ) {
3256 $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3257 } else {
3258 $icon_html = '';
3259 $icon_url = $data['image_icon']['source'] ?? '';
3260 $icon_id = $data['image_icon']['id'] ?? 0;
3261 $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3262
3263 if ( 'svg' === $extension ) {
3264 $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3265 $is_svg = strpos( $content, '<svg' );
3266
3267 if ( false !== $is_svg ) {
3268 $icon_html = substr( $content, $is_svg );
3269 }
3270 } else {
3271 $attr = [
3272 'class' => 'rtsb-icon-img',
3273 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3274 ];
3275
3276 $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3277 }
3278
3279 $html .= $icon_html;
3280 }
3281
3282 $html .= $wrapper ? '</span>' : '';
3283
3284 return $html;
3285 }
3286 /**
3287 * Flushes rewrite rules.
3288 *
3289 * @return void
3290 */
3291 public static function maybe_flush_rewrite_rules() {
3292 add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3293 add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3294 }
3295
3296 /**
3297 * Flushes rewrite rules if needed.
3298 *
3299 * @return void
3300 */
3301 public static function flush_rewrite_rules() {
3302 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3303 flush_rewrite_rules();
3304 }
3305
3306 if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3307 flush_rewrite_rules();
3308 delete_option( 'rtsb_permalinks_flushed' );
3309 }
3310 }
3311
3312 /**
3313 * Flushes rewrite rules if needed for second time.
3314 *
3315 * @return void
3316 */
3317 public static function flush_rewrite_rules_once_more() {
3318 if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3319 flush_rewrite_rules();
3320 update_option( 'rtsb_permalinks_flushed', 'yes' );
3321 delete_option( 'rtsb_permalinks_need_flush' );
3322 }
3323 }
3324
3325 /**
3326 * Social Share Platforms.
3327 *
3328 * @param string $section_id Section ID.
3329 * @param string $block_id Block ID.
3330 * @param string $option_key Option key.
3331 * @param string $compare_key Compare key.
3332 * @param string $compare_value Compare value.
3333 * @param array $values Values.
3334 *
3335 * @return void
3336 */
3337 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3338 $modules = DataModel::source()->get_option( $section_id, [], false );
3339 if ( empty( $modules[ $block_id ] ) ) {
3340 return;
3341 }
3342 $options = $modules[ $block_id ];
3343 if ( empty( $options[ $option_key ] ) ) {
3344 return;
3345 }
3346 $repeater_options = json_decode( $options[ $option_key ], true );
3347 // Check if options are not empty and are in array format.
3348 if ( ! is_array( $repeater_options ) ) {
3349 return;
3350 }
3351 // Use array_column to get an array of values from $compare_key.
3352 $column_values = array_column( $repeater_options, $compare_key );
3353 // Use array_search to find the index of the $compare_value.
3354 $index = array_search( $compare_value, $column_values, true );
3355 if ( false === $index ) {
3356 return;
3357 }
3358 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3359 $options[ $option_key ] = wp_json_encode( $repeater_options );
3360 $modules[ $block_id ] = $options;
3361 DataModel::source()->set_option( $section_id, $modules );
3362 }
3363
3364
3365 /**
3366 * @param array $ids products id.
3367 * @return array
3368 */
3369 public static function get_available_products_by_ids( $ids = [] ) {
3370 $ids = array_filter(
3371 $ids,
3372 function ( $id ) {
3373 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3374 }
3375 );
3376 return $ids;
3377 }
3378
3379 /**
3380 * Generate and cache dynamic CSS styles.
3381 *
3382 * @param array $options CSS options to apply (e.g. padding, margin).
3383 * @param string $cache_key Cache key for the CSS.
3384 * @param array $css_properties An array of CSS properties with selectors and properties.
3385 *
3386 * @return void
3387 */
3388 public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3389 $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3390 $style_handle = self::optimized_handle( 'rtsb-frontend' );
3391
3392 if ( false !== $cached_css ) {
3393 wp_add_inline_style( $style_handle, $cached_css );
3394 return;
3395 }
3396
3397 $css_rules = [];
3398 $grouped_css_rules = [];
3399
3400 foreach ( $css_properties as $option => $details ) {
3401 if ( ! empty( $options[ $option ] ) ) {
3402 $selector = $details['selector'] ?? '';
3403 $property = $details['property'] ?? '';
3404 $unit = $details['unit'] ?? '';
3405 $value = $options[ $option ];
3406
3407 if ( empty( $grouped_css_rules[ $selector ] ) ) {
3408 $grouped_css_rules[ $selector ] = [];
3409 }
3410
3411 if ( is_array( $property ) ) {
3412 foreach ( $property as $prop ) {
3413 $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3414 }
3415 } else {
3416 $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3417 }
3418 }
3419 }
3420
3421 foreach ( $grouped_css_rules as $selector => $properties ) {
3422 $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3423 }
3424
3425 $dynamic_css = implode( ' ', $css_rules );
3426
3427 wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3428 Cache::set_data_cache_key( $cache_key );
3429
3430 if ( ! empty( $dynamic_css ) ) {
3431 wp_add_inline_style( $style_handle, $dynamic_css );
3432 }
3433 }
3434
3435 /**
3436 * Pro version notice.
3437 *
3438 * @param string $ver Pro Version.
3439 * @param string $tab Tab.
3440 * @param string $name Name.
3441 * @param bool $enable_free_Link Enable free link.
3442 *
3443 * @return array[]
3444 */
3445 public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3446 return [
3447 'version_check' => [
3448 'id' => 'version_check',
3449 'type' => 'title',
3450 'label' => sprintf(
3451 /* translators: 1: required version, 2: link to the Plugins page */
3452 esc_html__(
3453 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3454 'shopbuilder'
3455 ),
3456 $name,
3457 sprintf(
3458 '<br /><u><b>%s</b></u>',
3459 esc_html( $ver ?? '1.5.0' ) . ' or higher'
3460 ),
3461 $enable_free_Link
3462 ? sprintf(
3463 '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3464 esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3465 esc_url( admin_url( 'plugins.php' ) ),
3466 esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3467 esc_html__( 'from the Plugins page', 'shopbuilder' )
3468 )
3469 : ''
3470 ),
3471 'tab' => $tab,
3472 'customClass' => 'checkout-notice',
3473 ],
3474 ];
3475 }
3476
3477 /**
3478 * Get product gallery ids.
3479 *
3480 * @param object $product Product object.
3481 *
3482 * @return mixed
3483 */
3484 public static function get_cached_gallery_ids( $product ) {
3485 $product_id = $product->get_id();
3486 $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3487
3488 if ( isset( self::$cache[ $cache_key ] ) ) {
3489 return self::$cache[ $cache_key ];
3490 }
3491
3492 $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3493
3494 if ( false !== $cached_result ) {
3495 self::$cache[ $cache_key ] = $cached_result;
3496
3497 return $cached_result;
3498 }
3499
3500 $gallery_ids = $product->get_gallery_image_ids();
3501 self::$cache[ $cache_key ] = $gallery_ids;
3502
3503 wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3504 Cache::set_data_cache_key( $cache_key );
3505
3506 return $gallery_ids;
3507 }
3508
3509 /**
3510 * Check if optimization settings are enabled.
3511 *
3512 * @return bool
3513 */
3514 public static function is_optimization_enabled() {
3515 $has_pro = rtsb()->has_pro();
3516 $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3517
3518 if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3519 return false;
3520 }
3521
3522 $generalList = GeneralList::instance()->get_data();
3523
3524 return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3525 }
3526
3527 /**
3528 * Get the optimized handle.
3529 *
3530 * @param string $handle Base handle used for script/style IDs.
3531 *
3532 * @return string
3533 */
3534 public static function optimized_handle( $handle ) {
3535 $use_optimization = self::is_optimization_enabled();
3536
3537 if ( $use_optimization ) {
3538 $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3539 }
3540
3541 return $handle;
3542 }
3543
3544 /**
3545 * Get the optimized handle by context.
3546 *
3547 * @return string
3548 */
3549 public static function get_optimized_handle_by_context() {
3550 $context = self::detect_context();
3551
3552 if ( 'account' === $context ) {
3553 return 'rtsb-bundled-global';
3554 }
3555
3556 return "rtsb-bundled-$context";
3557 }
3558
3559 /**
3560 * Check if Elementor page.
3561 *
3562 * @param int $post_id Post ID.
3563 *
3564 * @return bool
3565 */
3566 public static function is_elementor_page( $post_id = null ) {
3567 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3568 return false;
3569 }
3570
3571 $post_id = $post_id ?: get_the_ID();
3572
3573 if ( ! $post_id ) {
3574 return true;
3575 }
3576
3577 return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3578 }
3579
3580 /**
3581 * Check if shop or archive.
3582 *
3583 * @return bool
3584 */
3585 public static function is_shop_or_archive() {
3586 return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3587 }
3588
3589 /**
3590 * Detect context.
3591 *
3592 * @return string
3593 */
3594 public static function detect_context() {
3595 switch ( true ) {
3596 case is_product() || BuilderFns::is_product():
3597 $context = 'product';
3598 break;
3599
3600 case is_cart() || BuilderFns::is_cart():
3601 $context = 'cart';
3602 break;
3603
3604 case is_checkout() || BuilderFns::is_checkout():
3605 $context = 'checkout';
3606 break;
3607
3608 case self::is_shop_or_archive():
3609 $context = 'shop';
3610 break;
3611
3612 default:
3613 $context = 'global';
3614 break;
3615 }
3616
3617 return apply_filters( 'rtsb/optimizer/context_detect', $context );
3618 }
3619
3620 /**
3621 * Enqueue optimized assets.
3622 *
3623 * @return void
3624 */
3625 public static function enqueue_optimized_assets() {
3626 $asset_registry = AssetRegistry::instance();
3627 $bundled_assets = $asset_registry->get_bundled_assets();
3628 $context = self::detect_context();
3629 $contextual_loading = self::is_contextual_loading();
3630 $theme_handle = self::find_theme_stylesheet_handle();
3631
3632 if ( $contextual_loading ) {
3633 // Enqueue JS.
3634 if ( ! empty( $bundled_assets['js'] ) ) {
3635 $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3636
3637 if ( $js_context ) {
3638 wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3639 }
3640 }
3641
3642 // Enqueue CSS.
3643 if ( ! empty( $bundled_assets['css'] ) ) {
3644 $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3645
3646 if ( $css_context ) {
3647 $handle = $bundled_assets['css'][ $css_context ]['handle'];
3648
3649 wp_enqueue_style( $handle );
3650
3651 if ( ! empty( $theme_handle ) ) {
3652 self::set_theme_dependency( $theme_handle, $handle );
3653 }
3654 }
3655 }
3656 } else {
3657 // Enqueue JS.
3658 if ( ! empty( $bundled_assets['js'] ) ) {
3659 wp_enqueue_script( $bundled_assets['js']['handle'] );
3660 }
3661
3662 // Enqueue CSS.
3663 if ( ! empty( $bundled_assets['css'] ) ) {
3664 $handle = $bundled_assets['css']['handle'];
3665
3666 wp_enqueue_style( $handle );
3667
3668 if ( ! empty( $theme_handle ) ) {
3669 self::set_theme_dependency( $theme_handle, $handle );
3670 }
3671 }
3672 }
3673 }
3674
3675 /**
3676 * Find theme stylesheet handle.
3677 *
3678 * @return string|false
3679 */
3680 private static function find_theme_stylesheet_handle() {
3681 static $cached_handle = null;
3682
3683 if ( null !== $cached_handle ) {
3684 return $cached_handle;
3685 }
3686
3687 global $wp_styles;
3688
3689 if ( ! $wp_styles instanceof WP_Styles ) {
3690 return false;
3691 }
3692
3693 $stylesheet_uri = get_stylesheet_uri();
3694 $theme_directory_uri = get_stylesheet_directory_uri();
3695
3696 $hash = md5( $stylesheet_uri );
3697 $transient_key = 'rtsb_theme_css_handle_' . $hash;
3698
3699 $transient = get_transient( $transient_key );
3700
3701 if ( false !== $transient ) {
3702 $cached_handle = $transient;
3703
3704 return $transient;
3705 }
3706
3707 foreach ( $wp_styles->registered as $handle => $style ) {
3708 $src = $style->src;
3709
3710 if (
3711 ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3712 strpos( $src, 'style.css' ) !== false
3713 ) {
3714 set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3715 Cache::set_transient_cache_key( $transient_key );
3716 $cached_handle = $handle;
3717
3718 return $handle;
3719 }
3720 }
3721
3722 $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3723
3724 if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3725 set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3726 Cache::set_transient_cache_key( $transient_key );
3727 $cached_handle = $filtered_handle;
3728
3729 return $filtered_handle;
3730 }
3731
3732 set_transient( $transient_key, false, DAY_IN_SECONDS );
3733 Cache::set_transient_cache_key( $transient_key );
3734 $cached_handle = false;
3735
3736 return false;
3737 }
3738
3739 /**
3740 * Set theme dependency.
3741 *
3742 * @param string $theme_handle Theme handle.
3743 * @param string $our_handle Our handle.
3744 *
3745 * @return void
3746 */
3747 private static function set_theme_dependency( $theme_handle, $our_handle ) {
3748 global $wp_styles;
3749
3750 if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3751 return;
3752 }
3753
3754 $theme_style = $wp_styles->registered[ $theme_handle ];
3755
3756 // Add our handle as a dependency if it's not already there.
3757 if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3758 $theme_style->deps[] = $our_handle;
3759 }
3760 }
3761
3762 /**
3763 * Check if Elementor scripts should be loaded.
3764 *
3765 * @return bool
3766 */
3767 public static function should_load_elementor_scripts() {
3768 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3769
3770 $enable_optimization = $data['enable_optimization'] ?? 'on';
3771 $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3772
3773 if ( 'on' !== $enable_optimization ) {
3774 return true;
3775 }
3776
3777 return 'on' === $load_elementor_scripts;
3778 }
3779
3780 /**
3781 * Locate asset.
3782 *
3783 * @param string $relative_path Relative path.
3784 *
3785 * @return string|null
3786 */
3787 public static function locate_asset( $relative_path ) {
3788 $free_context = rtsb();
3789 $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3790
3791 $paths = [];
3792
3793 if ( $pro_context ) {
3794 $paths[] = $pro_context->get_assets_path( $relative_path );
3795 }
3796
3797 $paths[] = $free_context->get_assets_path( $relative_path );
3798
3799 foreach ( $paths as $path ) {
3800 if ( file_exists( $path ) ) {
3801 return $path;
3802 }
3803 }
3804
3805 return null;
3806 }
3807
3808 /**
3809 * Enqueue module assets.
3810 *
3811 * @param string $handle Asset handle.
3812 * @param string $module_name Module name.
3813 * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3814 *
3815 * @return string
3816 */
3817 public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3818 $use_optimization = self::is_optimization_enabled();
3819 $handle = self::optimized_handle( $handle );
3820
3821 if ( $use_optimization ) {
3822 return $handle;
3823 }
3824
3825 $defaults = [
3826 'type' => 'both',
3827 'deps' => [ 'jquery', 'rtsb-public' ],
3828 'context' => null,
3829 'version' => RTSB_VERSION,
3830 ];
3831
3832 $config = array_merge( $defaults, $options );
3833 $rtl_suffix = is_rtl() ? '-rtl' : '';
3834 $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3835 $context = $config['context'] ?: rtsb();
3836 $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3837 $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3838
3839 // Register CSS if enabled.
3840 if ( $load_css ) {
3841 wp_register_style(
3842 $handle,
3843 $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3844 [],
3845 $config['version']
3846 );
3847 }
3848
3849 // Register JS if enabled.
3850 if ( $load_js ) {
3851 wp_register_script(
3852 $handle,
3853 $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3854 $config['deps'],
3855 $config['version'],
3856 true
3857 );
3858 }
3859
3860 if ( $load_css ) {
3861 wp_enqueue_style( $handle );
3862
3863 $theme_handle = self::find_theme_stylesheet_handle();
3864
3865 if ( ! empty( $theme_handle ) ) {
3866 self::set_theme_dependency( $theme_handle, $handle );
3867 }
3868 }
3869
3870 if ( $load_js ) {
3871 wp_enqueue_script( $handle );
3872 }
3873
3874 return $handle;
3875 }
3876
3877 /**
3878 * Check if contextual loading is enabled.
3879 *
3880 * @return bool
3881 */
3882 public static function is_contextual_loading() {
3883 $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3884
3885 return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3886 }
3887
3888 /**
3889 * Get Modules list with cache support.
3890 *
3891 * @return array
3892 */
3893 public static function get_modules_list() {
3894 static $cached_modules = null;
3895
3896 if ( null !== $cached_modules ) {
3897 return $cached_modules;
3898 }
3899
3900 $cache_key = 'rtsb_module_list';
3901 $cache_group = 'shopbuilder';
3902
3903 $cached = wp_cache_get( $cache_key, $cache_group );
3904
3905 if ( false !== $cached ) {
3906 $cached_modules = $cached;
3907
3908 return $cached;
3909 }
3910
3911 $modules = ModuleList::instance()->get_data();
3912
3913 wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3914 Cache::set_data_cache_key( $cache_key );
3915
3916 $cached_modules = $modules;
3917
3918 return $modules;
3919 }
3920
3921 /**
3922 * Get Elementor widgets list with cache support.
3923 *
3924 * @return array
3925 */
3926 public static function get_widgets_list() {
3927 static $cached_widgets = null;
3928
3929 if ( null !== $cached_widgets ) {
3930 return $cached_widgets;
3931 }
3932
3933 $cache_key = 'rtsb_elementor_widget_list';
3934 $cache_group = 'shopbuilder';
3935
3936 $cached = wp_cache_get( $cache_key, $cache_group );
3937
3938 if ( false !== $cached ) {
3939 $cached_widgets = $cached;
3940
3941 return $cached;
3942 }
3943
3944 $widgets = ElementList::instance()->get_list();
3945
3946 wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
3947 Cache::set_data_cache_key( $cache_key );
3948
3949 $cached_widgets = $widgets;
3950
3951 return $widgets;
3952 }
3953
3954 /**
3955 * Convert the given price to the active currency.
3956 *
3957 * @param float $price The original price.
3958 * @param Object||null $product Product.
3959 *
3960 * @return float
3961 */
3962 public static function get_currency_base_price( $price, $product = null ) {
3963 return apply_filters( 'rtsb/convert/currency/price', $price, $product );
3964 }
3965 /**
3966 * Generate a signed URL with a payload.
3967 *
3968 * @param array $payload Associative data to encode.
3969 * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
3970 * @param string $key Key name to use in the URL.
3971 * @return string Signed URL with key parameter.
3972 */
3973 public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
3974 if ( empty( $key ) ) {
3975 $key = 'key';
3976 }
3977 // Convert payload to JSON.
3978 $data = wp_json_encode( $payload );
3979 // Create signature.
3980 $signature = wp_hash( $data );
3981 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
3982 $encode_key = base64_encode( $data . '::' . $signature );
3983 // Return full URL with key param.
3984 return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
3985 }
3986
3987 /**
3988 * Decode and verify a signed URL key.
3989 *
3990 * @param string $key Encoded key from URL.
3991 * @return array|false Decoded payload array on success, false on failure.
3992 */
3993 public static function decode_signed_key( string $key ) {
3994 if ( empty( $key ) ) {
3995 return false;
3996 }
3997 $key = rawurldecode( wp_unslash( $key ) );
3998 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
3999 $raw = base64_decode( sanitize_text_field( $key ) );
4000 if ( ! $raw ) {
4001 return false;
4002 }
4003 $parts = explode( '::', $raw, 2 );
4004 if ( count( $parts ) !== 2 ) {
4005 return false;
4006 }
4007 list( $data_json, $signature ) = $parts;
4008 // Validate signature.
4009 if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4010 return false;
4011 }
4012 $payload = json_decode( $data_json, true );
4013 return is_array( $payload ) ? $payload : false;
4014 }
4015
4016 /**
4017 * Get Loco Translate MO file path for a plugin.
4018 *
4019 * @param string $textdomain Plugin textdomain.
4020 * @return void|false
4021 */
4022 public static function load_loco_textdomain( $textdomain ) {
4023 if ( ! function_exists( 'loco_plugin_version' ) ) {
4024 return false;
4025 }
4026 $lang = WP_LANG_DIR;
4027 $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4028 if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4029 $lang = LOCO_LANG_DIR;
4030 $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4031 }
4032 if ( ! file_exists( $path ) ) {
4033 if ( 'shopbuilder' === $textdomain ) {
4034 $plugin_root = dirname( RTSB_FILE );
4035 } elseif ( 'shopbuilder-pro' === $textdomain ) {
4036 $plugin_root = dirname( RTSBPRO_FILE );
4037 } else {
4038 return false;
4039 }
4040 $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4041 }
4042 if ( ! file_exists( $path ) ) {
4043 return false;
4044 }
4045 load_textdomain( $textdomain, $path );
4046 }
4047 }
4048