PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
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.6, at app/Helpers/Fns.php

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