PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.3
ShopBuilder – WooCommerce Builder For Elementor v3.2.3
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
← All changes | app/Helpers/Fns.php +1233 -137 2.2.13.2.3 View file →
@@ -11,15 +11,23 @@
11 11 if ( ! defined( 'ABSPATH' ) ) {
12 12 exit( 'This script cannot be accessed directly.' );
13 13 }
14 14
15 +
16 +use CodesVault\Howdyqb\DB;
17 +use DateTime;
18 +use WP_Styles;
15 19 use WC_Product;
20 +use Elementor\Plugin;
16 21 use WC_Product_Query;
17 22 use Elementor\Icons_Manager;
18 23 use RadiusTheme\SB\Models\ReSizer;
24 +use RadiusTheme\SB\Models\Settings;
19 25 use RadiusTheme\SB\Models\DataModel;
20 26 use RadiusTheme\SB\Models\ModuleList;
21 -use RadiusTheme\SB\Models\Settings;
27 +use RadiusTheme\SB\Models\GeneralList;
28 +use RadiusTheme\SB\Models\ElementList;
29 +use RadiusTheme\SB\Controllers\AssetRegistry;
22 30
23 31 /**
24 32 * Fns class
25 33 */
@@ -28,8 +36,22 @@
28 36 /**
29 37 * @var array
30 38 */
31 39 private static $cache = [];
40 + /**
41 + * Check if the stored license is valid.
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 + }
32 54
33 55 /**
34 56 * Verify nonce.
35 57 *
@@ -44,19 +66,46 @@
44 66
45 67 return false;
46 68 }
47 69
70 + /**
71 + * Get nonce.
72 + *
73 + * @return string|null
74 + */
75 + public static function enable_loader() {
76 + return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
77 + }
78 + /**
79 + * Get nonce.
80 + *
81 + * @return string|null
82 + */
83 + public static function content_invisible() {
84 + $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
85 + if ( $wp_rocket_compatibility ) {
86 + return true;
87 + }
88 + if ( defined( 'WP_ROCKET_VERSION' ) ) {
89 + return false;
90 + }
91 + return true;
92 + }
93 + /**
94 + * Get nonce.
95 + *
96 + * @return string|null
97 + */
48 98 public static function get_nonce() {
49 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
99 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
50 100 return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
51 101 }
52 102
53 -
54 103 /**
55 - * Set Cookie or Session
104 + * Set Session
56 105 *
57 - * @param $name
58 - * @param $value
106 + * @param string $name Name.
107 + * @param mixed $value Value.
59 108 */
60 109 public static function setSession( $name, $value ) {
61 110 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 111 session_start();
@@ -64,12 +113,13 @@
64 113 } else {
65 114 $_SESSION[ $name ] = $value;
66 115 }
67 116 }
117 +
68 118 /**
69 119 * Get Cookie or Session
70 120 *
71 - * @param $name
121 + * @param string $name Name.
72 122 *
73 123 * @return bool
74 124 */
75 125 public static function getSession( $name ) {
@@ -75,9 +125,9 @@
75 125 public static function getSession( $name ) {
76 126 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
77 127 session_start();
78 128 }
79 - return $_SESSION[ $name ] ?? null;
129 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
80 130 }
81 131 /**
82 132 * Remove Session Variable
83 133 *
@@ -90,12 +140,43 @@
90 140 } else {
91 141 unset( $_SESSION[ $name ] );
92 142 }
93 143 }
94 -
95 144 /**
96 - * @param $plugin_slug
145 + * Gets the current timestamp in WordPress timezone.
97 146 *
147 + * @return int Current timestamp.
148 + */
149 + public static function currentTimestampUTC() {
150 + $wp_timezone = wp_timezone();
151 + $now = new DateTime( 'now', $wp_timezone ); // Current time in WP timezone.
152 + return $now->getTimestamp();
153 + }
154 + /**
155 + * Action.
156 + *
157 + * @param string $action action.
158 + * @return void
159 + */
160 + public static function add_to_scheduled_hook_list( $action ) {
161 + if ( empty( $action ) ) {
162 + return;
163 + }
164 + $schedule = get_option( 'rtsb_cron_schedule_free', [] );
165 + $schedule[] = $action;
166 + update_option( 'rtsb_cron_schedule_free', array_unique( $schedule ) );
167 + }
168 + /**
169 + * @return DB
170 + */
171 + public static function DB() {
172 + return new DB( 'wpdb' );
173 + }
174 + /**
175 + * Check if a plugin is installed.
176 + *
177 + * @param string $plugin_slug Plugin slug.
178 + *
98 179 * @return bool
99 180 */
100 181 public static function check_plugin_installed( $plugin_slug ): bool {
101 182 $installed_plugins = get_plugins();
@@ -103,10 +184,12 @@
103 184 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
104 185 }
105 186
106 187 /**
107 - * @param $plugin_slug
188 + * Check if a plugin is active.
108 189 *
190 + * @param string $plugin_slug Plugin slug.
191 + *
109 192 * @return bool
110 193 */
111 194 public static function check_plugin_active( $plugin_slug ): bool {
112 195 if ( is_plugin_active( $plugin_slug ) ) {
@@ -114,21 +197,71 @@
114 197 }
115 198
116 199 return false;
117 200 }
201 + /**
202 + * Get all user roles.
203 + *
204 + * @return array
205 + */
206 + public static function get_current_user_roles() {
207 + $current_user = wp_get_current_user();
208 + return $current_user->roles;
209 + }
210 + /**
211 + * Get all user roles.
212 + *
213 + * @return array|false|mixed
214 + */
215 + public static function get_all_user_roles() {
216 + $cache_key = 'rtsb_get_user_roles';
217 + $roles = wp_cache_get( $cache_key, 'shopbuilder' );
118 218
219 + if ( empty( $roles ) ) {
220 + if ( ! function_exists( 'get_editable_roles' ) ) {
221 + require_once ABSPATH . 'wp-admin/includes/user.php';
222 + }
223 +
224 + $user_roles = \get_editable_roles();
225 + $roles = [];
226 +
227 + foreach ( $user_roles as $key => $role ) {
228 + if ( empty( $role['capabilities'] ) ) {
229 + continue;
230 + }
231 + $roles[ $key ] = $role['name'];
232 + }
233 + }
234 + wp_cache_set( $cache_key, $roles, 'shopbuilder' );
235 + Cache::set_data_cache_key( $cache_key );
236 + return $roles;
237 + }
238 +
119 239 /**
120 - * @param $data
240 + * Stripslashes value.
121 241 *
242 + * @param mixed $data Data.
243 + *
122 244 * @return mixed|string
123 245 */
124 246 public static function stripslashes_value( $data ) {
125 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
126 - return stripslashes( $data );
127 - } elseif ( is_array( $data ) ) {
247 + if ( is_array( $data ) ) {
128 248 foreach ( $data as $key => $value ) {
129 249 $data[ $key ] = self::stripslashes_value( $value );
130 250 }
251 + } elseif ( is_string( $data ) ) {
252 + $trimmed = trim( $data );
253 + // Try to decode JSON.
254 + $json = json_decode( $trimmed, true );
255 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
256 + // Recursively stripslashes on decoded array.
257 + $json = self::stripslashes_value( $json );
258 + return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
259 + }
260 + // Not valid JSON, just stripslashes if needed.
261 + if ( strpos( $data, '\\' ) !== false ) {
262 + return stripslashes( $data );
263 + }
131 264 }
132 265
133 266 return $data;
134 267 }
@@ -133,10 +266,12 @@
133 266 return $data;
134 267 }
135 268
136 269 /**
137 - * @param $string
270 + * Multiselect settings field value
138 271 *
272 + * @param string $string String.
273 + *
139 274 * @return array
140 275 */
141 276 public static function multiselect_settings_field_value( $string ) {
142 277
@@ -156,8 +291,10 @@
156 291 return $values;
157 292 }
158 293
159 294 /**
295 + * Check if is block theme
296 + *
160 297 * @return bool
161 298 */
162 299 public static function check_is_block_theme(): bool {
163 300 global $wp_version;
@@ -165,8 +302,10 @@
165 302 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
166 303 }
167 304
168 305 /**
306 + * Locate template
307 + *
169 308 * @param string $template_name Template name.
170 309 * @param string $template_path Template path. (default: '').
171 310 * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
172 311 *
@@ -197,12 +336,12 @@
197 336 )
198 337 );
199 338
200 339 if ( ! $located ) {
201 - if ( file_exists( $plugin_path ) ) {
340 + if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
341 + return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
342 + } elseif ( file_exists( $plugin_path ) ) {
202 343 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
203 - } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
204 - return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
205 344 }
206 345 }
207 346
208 347 /**
@@ -239,9 +378,9 @@
239 378 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
240 379 *
241 380 * @return false|string|void
242 381 */
243 - public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
382 + public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
244 383 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
245 384 $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
246 385 if ( ! $located ) {
247 386 $located = self::locate_template( $template_name, $template_path, $plugin_path );
@@ -260,9 +399,9 @@
260 399 }
261 400
262 401 if ( ! empty( $args ) && is_array( $args ) ) {
263 402 $atts = $args;
264 - extract( $args ); // @codingStandardsIgnoreLine
403 + extract( $args ); // @codingStandardsIgnoreLine
265 404 }
266 405
267 406 // Allow 3rd party plugin filter template file from their plugin.
268 407 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -377,9 +516,9 @@
377 516 // Return the Builder Template id.
378 517
379 518 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
380 519 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
381 - if ( $product_id ) {
520 + if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
382 521 return $product_id;
383 522 }
384 523 }
385 524
@@ -386,9 +525,9 @@
386 525 global $wpdb;
387 526 $cache_key = 'rtsb_prepared_product_id';
388 527 $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
389 528 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
390 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
529 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
391 530 $_post_id = $wpdb->get_var(
392 531 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
393 532 );
394 533 wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
@@ -406,9 +545,8 @@
406 545 public static function get_product() {
407 546 global $product;
408 547
409 548 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
410 - // do_action( 'rtsb_before_product_template_render' );.
411 549 return $product;
412 550 }
413 551 $cache_key = 'prepared_product_for_preview';
414 552 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -429,8 +567,9 @@
429 567 *
430 568 * @return void
431 569 */
432 570 public static function doing_it_wrong( $function, $message, $version ) {
571 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
433 572 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
434 573 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
435 574 }
436 575
@@ -448,8 +587,15 @@
448 587
449 588 return $pages;
450 589 }
451 590
591 + /**
592 + * Get section items
593 + *
594 + * @param string $section_id Section ID.
595 + *
596 + * @return array
597 + */
452 598 public static function get_section_items( $section_id ) {
453 599 if ( ! $section_id ) {
454 600 return [];
455 601 }
@@ -456,8 +602,16 @@
456 602
457 603 return DataModel::source()->get_option( $section_id, [] );
458 604 }
459 605
606 + /**
607 + * Get options items
608 + *
609 + * @param string $section_id Section ID.
610 + * @param string $item_id Item ID.
611 + *
612 + * @return array
613 + */
460 614 public static function get_options( $section_id, $item_id ) {
461 615 if ( ! $section_id || ! $item_id ) {
462 616 return [];
463 617 }
@@ -468,11 +622,11 @@
468 622
469 623 /**
470 624 * Get options value with default value if options doesn't exist.
471 625 *
472 - * @param $group
473 - * @param $option_key
474 - * @param $default_value
626 + * @param array $group Group.
627 + * @param string $option_key Option key.
628 + * @param string $default_value Default value.
475 629 *
476 630 * @return mixed|string
477 631 */
478 632 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -484,32 +638,34 @@
484 638 return $group[ $option_key ];
485 639 }
486 640
487 641 /**
488 - * @param string $section_id
489 - * @param string $item_id
490 - * @param string $option_id
491 - * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value
492 - * @param null $type checkbox, multi_checkbox, number
642 + * Get option.
493 643 *
644 + * @param string $section_id Section ID.
645 + * @param string $item_id Item ID.
646 + * @param string $option_id Option ID.
647 + * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
648 + * @param null $type checkbox, multi_checkbox, number.
649 + *
494 650 * @return bool|int|mixed|null
495 651 */
496 652 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
497 653 $options = self::get_options( $section_id, $item_id );
498 654
499 - if ( $type === 'checkbox' ) {
655 + if ( 'checkbox' === $type ) {
500 656 if ( isset( $options[ $option_id ] ) ) {
501 - return $options[ $option_id ] === 'on';
657 + return 'on' === $options[ $option_id ];
502 658 }
503 659
504 660 return $default;
505 - } elseif ( $type === 'multi_checkbox' ) {
506 - return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] );
507 - } elseif ( $type === 'number' ) {
661 + } elseif ( 'multi_checkbox' === $type ) {
662 + return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
663 + } elseif ( 'number' === $type ) {
508 664 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
509 665 }
510 666
511 - return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
667 + return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
512 668 }
513 669
514 670
515 671 /**
@@ -564,9 +720,9 @@
564 720
565 721 if ( strlen( $page_content ) > 0 ) {
566 722 // Search for an existing page with the specified page content (typically a shortcode).
567 723 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
568 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
724 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
569 725 $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}%" ) );
570 726 } else {
571 727 // Search for an existing page with the specified page slug.
572 728 $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
@@ -636,8 +792,13 @@
636 792
637 793 return $page_id;
638 794 }
639 795
796 + /**
797 + * Get allowed HTML tags.
798 + *
799 + * @return array
800 + */
640 801 public static function get_kses_array() {
641 802 return [
642 803 'a' => [
643 804 'class' => [],
@@ -736,13 +897,14 @@
736 897 ];
737 898 }
738 899
739 900
740 - /*
901 + /**
741 902 * Escape output of wishlist icon
742 903 *
743 904 * @param string $data Data to escape.
744 - * @return string Escaped data
905 + *
906 + * @return void
745 907 */
746 908 public static function print_icon( $data ) {
747 909 /**
748 910 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -826,9 +988,9 @@
826 988 * @return mixed
827 989 */
828 990 public static function allowedHtml( $level = 'basic' ) {
829 991 $allowed_html = [];
830 - // TODO:: Need Optimize.
992 +
831 993 switch ( $level ) {
832 994 case 'basic':
833 995 $allowed_html = [
834 996 'b' => [
@@ -968,13 +1130,15 @@
968 1130 return $allowed_html;
969 1131 }
970 1132
971 1133 /**
972 - * Safe print a validated HTML tag.
1134 + * Safe get a validated HTML tag.
973 1135 *
974 - * @param string $tag
1136 + * @param string $tag HTML tag.
1137 + *
1138 + * @return string
975 1139 */
976 - public static function print_validated_html_tag( $tag ) {
1140 + public static function get_validated_html_tag( $tag ) {
977 1141 $allowed_html_wrapper_tags = [
978 1142 'a',
979 1143 'article',
980 1144 'aside',
@@ -994,12 +1158,23 @@
994 1158 'section',
995 1159 'span',
996 1160 ];
997 1161
998 - self::print_html( in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div' );
1162 + return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
999 1163 }
1000 1164
1001 1165 /**
1166 + * Safe print a validated HTML tag.
1167 + *
1168 + * @param string $tag HTML tag.
1169 + *
1170 + * @return void
1171 + */
1172 + public static function print_validated_html_tag( $tag ) {
1173 + self::print_html( self::get_validated_html_tag( $tag ) );
1174 + }
1175 +
1176 + /**
1002 1177 * Insert Some array element
1003 1178 *
1004 1179 * @param [type] $key The elements will insert nearby this key.
1005 1180 * @param [type] $main_array Original array.
@@ -1062,11 +1237,17 @@
1062 1237
1063 1238 /**
1064 1239 * Free Layouts
1065 1240 *
1241 + * @param string $layout Layout.
1242 + *
1066 1243 * @return array
1067 1244 */
1068 - public static function free_layouts() {
1245 + public static function free_layouts( $layout = '' ) {
1246 + if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1247 + return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1248 + }
1249 +
1069 1250 $layouts = [
1070 1251 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1071 1252 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1072 1253 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -1120,8 +1301,25 @@
1120 1301 }
1121 1302 self::$cache[ $cache_key ] = $terms;
1122 1303 return $terms;
1123 1304 }
1305 + /**
1306 + * Get all product attribute taxonomy by id
1307 + *
1308 + * @param array $term_ids Term id.
1309 + *
1310 + * @return array
1311 + */
1312 + public static function tax_filter_attr_taxonomy( $term_ids ) {
1313 + $taxonomies = [];
1314 + foreach ( $term_ids as $id ) {
1315 + $term = get_term( $id );
1316 + if ( ! is_wp_error( $term ) && $term ) {
1317 + $taxonomies[] = $term->taxonomy;
1318 + }
1319 + }
1320 + return array_unique( $taxonomies );
1321 + }
1124 1322
1125 1323 /**
1126 1324 * Get all terms by attributes
1127 1325 *
@@ -1233,9 +1431,8 @@
1233 1431 if ( ! is_admin() ) {
1234 1432 return [];
1235 1433 }
1236 1434
1237 - // TODO:: Return Slug always true for all settings.
1238 1435 $term_list = [];
1239 1436 $args = [
1240 1437 'taxonomy' => $taxonomy,
1241 1438 'hide_empty' => false,
@@ -1305,9 +1502,9 @@
1305 1502
1306 1503 if ( $args['show_rating_count'] ) {
1307 1504 $count .= '<div class="rtsb-count">';
1308 1505 $count .= sprintf(
1309 - /* translators: %s is the number of reviews */
1506 + /* translators: %s is the number of reviews */
1310 1507 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1311 1508 $rating_count
1312 1509 );
1313 1510 $count .= '</div>';
@@ -1332,8 +1529,13 @@
1332 1529
1333 1530 self::print_html( $html, true );
1334 1531 }
1335 1532
1533 + /**
1534 + * Get product simple rating.
1535 + *
1536 + * @return void
1537 + */
1336 1538 public static function get_product_simple_rating_html() {
1337 1539 global $product;
1338 1540 $html = null;
1339 1541 $rating_count = $product->get_rating_count();
@@ -1445,9 +1647,42 @@
1445 1647
1446 1648 <?php
1447 1649 self::print_html( ob_get_clean() );
1448 1650 }
1651 + /**
1652 + * Brands HTML
1653 + *
1654 + * @param int $id Product ID.
1655 + * @param string $class Custom class.
1656 + *
1657 + * @return void|string
1658 + */
1659 + public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1660 + $terms = get_the_terms( $id, 'product_brand' );
1661 + if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1662 + return '';
1663 + }
1664 + ob_start();
1665 + ?>
1449 1666
1667 + <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1668 + <?php
1669 + $brand_list = get_the_term_list(
1670 + $id,
1671 + 'product_brand',
1672 + '<li class="rtsb-brand-list-item">',
1673 + '</li><li class="rtsb-brand-list-item">',
1674 + '</li>'
1675 + );
1676 +
1677 + self::print_html( $brand_list );
1678 + ?>
1679 + </ul>
1680 +
1681 + <?php
1682 + self::print_html( ob_get_clean() );
1683 + }
1684 +
1450 1685 /**
1451 1686 * Get Featured Image HTML.
1452 1687 *
1453 1688 * @param string $type Image type.
@@ -1550,9 +1785,9 @@
1550 1785
1551 1786 if ( ! $img_html ) {
1552 1787 $hwstring = image_hwstring( 160, 160 );
1553 1788 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1554 - $attr['class'] = 'default-img';
1789 + $attr['class'] = 'default-img ' . $hover_class;
1555 1790 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1556 1791 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1557 1792 $img_html = rtrim( "<img $hwstring" );
1558 1793
@@ -1643,9 +1878,9 @@
1643 1878 if ( empty( $control['value'] ) ) {
1644 1879 return '';
1645 1880 }
1646 1881 if ( is_array( $control['value'] ) ) {
1647 - $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) );
1882 + $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1648 1883 } else {
1649 1884 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1650 1885 }
1651 1886 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -1681,9 +1916,13 @@
1681 1916 public static function get_product_swatches( $type ) {
1682 1917 ?>
1683 1918 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1684 1919 <?php
1685 - do_action( 'rtwpvs_show_archive_variation' );
1920 + if ( class_exists( 'Rtwpvsp' ) ) {
1921 + do_action( 'rtwpvs_show_archive_variation' );
1922 + } else {
1923 + do_action( 'rtsb/vs/showcase/variation' );
1924 + }
1686 1925 ?>
1687 1926 </div>
1688 1927 <?php
1689 1928 }
@@ -1780,47 +2019,35 @@
1780 2019 public static function calculate_sale_percentage( $product = null ) {
1781 2020 if ( is_null( $product ) ) {
1782 2021 global $product;
1783 2022 }
1784 -
1785 2023 if ( ! $product instanceof WC_Product ) {
1786 2024 return '';
1787 2025 }
1788 -
1789 2026 if ( ! $product->is_on_sale() ) {
1790 2027 return '';
1791 2028 }
1792 -
1793 - $percentage = null;
1794 - $max_percentage = '';
1795 -
2029 + $max_percentage = 0;
1796 2030 if ( $product->is_type( 'simple' ) ) {
1797 - $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
2031 + $regular_price = (float) $product->get_regular_price();
2032 + $sale_price = (float) $product->get_sale_price();
2033 + if ( $regular_price > 0 && $sale_price > 0 ) {
2034 + $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2035 + }
1798 2036 } elseif ( $product->is_type( 'variable' ) ) {
1799 - $max_percentage = 0;
2037 + $prices = $product->get_variation_prices( true );
2038 + if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
2039 + foreach ( $prices['regular_price'] as $key => $regular_price ) {
2040 + $sale_price = $prices['sale_price'][ $key ];
1800 2041
1801 - foreach ( $product->get_children() as $child_id ) {
1802 -
1803 - $variation = wc_get_product( $child_id );
1804 -
1805 - $price = $variation->get_regular_price();
1806 - $sale = $variation->get_sale_price();
1807 -
1808 - if ( 0 !== $price && ! empty( $sale ) ) {
1809 - $percentage = ( $price - $sale ) / $price * 100;
2042 + if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
2043 + $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2044 + $max_percentage = max( $max_percentage, $percentage );
2045 + }
1810 2046 }
1811 -
1812 - if ( $percentage > $max_percentage ) {
1813 - $max_percentage = $percentage;
1814 - }
1815 2047 }
1816 2048 }
1817 -
1818 - if ( $max_percentage > 0 ) {
1819 - return round( $max_percentage );
1820 - }
1821 -
1822 - return 0;
2049 + return $max_percentage > 0 ? round( $max_percentage ) : 0;
1823 2050 }
1824 2051
1825 2052 /**
1826 2053 * Pagination
@@ -1837,15 +2064,15 @@
1837 2064
1838 2065 global $paged;
1839 2066
1840 2067 if ( is_front_page() ) {
1841 - $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
2068 + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1842 2069 } else {
1843 - $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
2070 + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1844 2071 }
1845 2072
1846 2073 if ( empty( $paged ) ) {
1847 - $paged = 1;
2074 + $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1848 2075 }
1849 2076
1850 2077 if ( '' === $pages ) {
1851 2078 global $wp_query;
@@ -2003,10 +2230,8 @@
2003 2230 } else {
2004 2231 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2005 2232 }
2006 2233
2007 - $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
2008 -
2009 2234 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2010 2235 $html .= $cart_html;
2011 2236 } else {
2012 2237 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -2011,9 +2236,11 @@
2011 2236 } else {
2012 2237 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2013 2238 }
2014 2239
2015 - $html .= '</' . esc_attr( $wrapper ) . '>';
2240 + if ( ! empty( $html ) ) {
2241 + $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2242 + }
2016 2243
2017 2244 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2018 2245 }
2019 2246
@@ -2114,9 +2341,9 @@
2114 2341 $link['social_network'] = 'Facebook';
2115 2342 $link['social_action'] = 'Share';
2116 2343 break;
2117 2344 case 'twitter':
2118 - $link['link'] = esc_url( 'https://twitter.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
2345 + $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'] );
2119 2346 $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>';
2120 2347 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2121 2348 $link['social_network'] = 'Twitter';
2122 2349 $link['social_action'] = 'Tweet';
@@ -2150,9 +2377,9 @@
2150 2377 $link['social_action'] = 'Share';
2151 2378 break;
2152 2379 case 'reddit':
2153 2380 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2154 - $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5_logos</title><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>';
2381 + $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>';
2155 2382 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2156 2383 $link['social_network'] = 'Reddit';
2157 2384 $link['social_action'] = 'Share';
2158 2385 break;
@@ -2240,27 +2467,45 @@
2240 2467
2241 2468 /**
2242 2469 * Social Share Platforms.
2243 2470 *
2244 - * @return mixed|null
2471 + * @param string $module Module.
2472 + *
2473 + * @return true|void
2245 2474 */
2246 2475 public static function is_module_active( $module ) {
2247 - $modulelist = ModuleList::instance()->get_data();
2476 + $modulelist = self::get_modules_list();
2477 +
2248 2478 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2249 2479 return true;
2250 2480 }
2251 2481 }
2252 2482
2483 + /**
2484 + * Elementor Widget Active.
2485 + *
2486 + * @param string $widget Elementor Widget.
2487 + *
2488 + * @return true|void
2489 + */
2490 + public static function is_elementor_widget_active( $widget ) {
2491 + $element_list = self::get_widgets_list();
2492 +
2493 + if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2494 + return true;
2495 + }
2496 + }
2497 +
2253 2498 /***
2254 2499 * Save Settings data
2255 2500 *
2256 - * @param $section_id
2257 - * @param $block_id
2258 - * @param $rawOptions
2501 + * @param string $section_id Section ID.
2502 + * @param string $block_id Block ID.
2503 + * @param array $rawOptions Raw Options.
2259 2504 *
2260 2505 * @return array
2261 2506 */
2262 - public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2507 + public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2263 2508 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2264 2509 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2265 2510 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2266 2511 $results = [
@@ -2286,9 +2531,9 @@
2286 2531 $fields = [];
2287 2532 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2288 2533 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2289 2534 }
2290 -
2535 + do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2291 2536 if ( empty( $fields ) ) {
2292 2537 if ( isset( $rawOptions['active'] ) ) {
2293 2538 $changed = true;
2294 2539 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2294,15 +2539,15 @@
2294 2539 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2295 2540 }
2296 2541 } else {
2297 2542 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2298 - if ( $raw_option_key === 'active' ) {
2543 + if ( 'active' === $raw_option_key ) {
2299 2544 $changed = true;
2300 - $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : '';
2545 + $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2301 2546 } else {
2302 2547 if ( isset( $fields[ $raw_option_key ] ) ) {
2303 2548 $field = $fields[ $raw_option_key ];
2304 - if ( $field['type'] === 'switch' ) {
2549 + if ( 'switch' === $field['type'] ) {
2305 2550 $value = 'on' === $raw_value ? 'on' : '';
2306 2551 } elseif ( 'repeaters' === $field['type'] ) {
2307 2552 $the_value = [];
2308 2553 $rep_title = [];
@@ -2307,10 +2552,14 @@
2307 2552 $the_value = [];
2308 2553 $rep_title = [];
2309 2554 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2310 2555 foreach ( $raw_value as $key => $value ) {
2311 - $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2312 - $cr = $the_value_decoded->title ?? '';
2556 + if ( is_string( $value ) ) {
2557 + $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2558 + } else {
2559 + $the_value_decoded = $value;
2560 + }
2561 + $cr = $the_value_decoded->title ?? '';
2313 2562 if ( in_array( $cr, $rep_title, true ) ) {
2314 2563 continue;
2315 2564 }
2316 2565 $rep_title[] = $cr;
@@ -2318,11 +2567,21 @@
2318 2567 }
2319 2568 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2320 2569 $the_value = $raw_value;
2321 2570 }
2322 - $value = wp_json_encode( $the_value );
2571 + $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2572 + } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2573 + $manual_field_value = [];
2574 + if ( is_array( $raw_value ) ) {
2575 + foreach ( $raw_value as $key => $value ) {
2576 + $manual_field_value[] = json_decode( stripslashes( $value ), true );
2577 + }
2578 + $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2579 + } elseif ( is_string( $raw_value ) ) {
2580 + $value = $raw_value;
2581 + }
2323 2582 } else {
2324 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2583 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2325 2584 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2326 2585 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2327 2586 $value = array_map( $field['sanitize_fn'], $raw_value );
2328 2587 } else {
@@ -2344,9 +2603,9 @@
2344 2603 $value = sanitize_text_field( $raw_value );
2345 2604 }
2346 2605 }
2347 2606 }
2348 - $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null;
2607 + $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
2349 2608 $changed = true;
2350 2609 }
2351 2610 }
2352 2611 }
@@ -2366,27 +2625,15 @@
2366 2625
2367 2626 return $results;
2368 2627 }
2369 2628
2370 - /***
2371 - * @param $meta_key
2372 - * @param $meta_value
2629 + /**
2630 + * Count products by taxonomies.
2373 2631 *
2374 - * @return mixed|void
2632 + * @param array $terms Terms.
2633 + * @param string $taxonomy Taxonomy.
2375 2634 *
2376 - *
2377 - * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2378 - * if( ! $meta_key || ! $meta_value ){
2379 - * return;
2380 - * }
2381 - * global $wpdb;
2382 - * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2383 - * $the_products = $wpdb->get_col( $prepare_guery );
2384 - * if( count( $the_products ) > 0 ){
2385 - * return $the_products[0];
2386 - * }
2387 - * return;
2388 - * }
2635 + * @return int|void
2389 2636 */
2390 2637 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2391 2638 if ( empty( $terms ) || ! is_array( $terms ) ) {
2392 2639 return;
@@ -2398,17 +2645,73 @@
2398 2645 ];
2399 2646
2400 2647 if ( 'product_cat' === $taxonomy ) {
2401 2648 $args['product_category_id'] = $terms;
2649 + } elseif ( 'product_brand' === $taxonomy ) {
2650 + $args['product_brand_id'] = $terms;
2402 2651 } else {
2403 2652 $args['product_tag_id'] = $terms;
2404 2653 }
2405 2654
2406 2655 $query = new WC_Product_Query( $args );
2656 +
2407 2657 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2408 2658 }
2409 2659
2410 2660 /**
2661 + * Count products by attribute terms.
2662 + *
2663 + * @param array $term_ids Term IDs.
2664 + * @param string $relation Relation.
2665 + *
2666 + * @return int
2667 + */
2668 + public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2669 + if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2670 + return 0;
2671 + }
2672 +
2673 + $terms_by_taxonomy = [];
2674 +
2675 + foreach ( $term_ids as $term_id ) {
2676 + $term = get_term( $term_id );
2677 + if ( ! is_wp_error( $term ) && $term ) {
2678 + if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2679 + $terms_by_taxonomy[ $term->taxonomy ] = [];
2680 + }
2681 + $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2682 + }
2683 + }
2684 +
2685 + if ( empty( $terms_by_taxonomy ) ) {
2686 + return 0;
2687 + }
2688 +
2689 + $args = [
2690 + 'status' => 'publish',
2691 + 'limit' => -1,
2692 + 'return' => 'ids',
2693 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2694 + 'relation' => strtoupper( $relation ),
2695 + ],
2696 + ];
2697 +
2698 + foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2699 + $args['tax_query'][] = [
2700 + 'taxonomy' => $taxonomy,
2701 + 'field' => 'term_id',
2702 + 'terms' => $terms,
2703 + 'operator' => 'IN',
2704 + ];
2705 + }
2706 +
2707 + $query = new WC_Product_Query( $args );
2708 + $products = $query->get_products();
2709 +
2710 + return count( $products );
2711 + }
2712 +
2713 + /**
2411 2714 * Check if product filters widget has ajax.
2412 2715 *
2413 2716 * @param string $page Page name.
2414 2717 *
@@ -2479,9 +2782,9 @@
2479 2782 if ( ! is_admin() ) {
2480 2783 return [];
2481 2784 }
2482 2785
2483 - $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) );
2786 + $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2484 2787
2485 2788 if ( isset( self::$cache[ $cache_key ] ) ) {
2486 2789 return self::$cache[ $cache_key ];
2487 2790 }
@@ -2523,9 +2826,65 @@
2523 2826 // Cache the results in a static variable for the next call.
2524 2827 self::$cache[ $cache_key ] = $cats;
2525 2828 return $cats;
2526 2829 }
2830 + /**
2831 + * Get WooCommerce product categories.
2832 + *
2833 + * @param string|null $search_query The search string for category names.
2834 + *
2835 + * @return array An array of product categories with 'value' and 'label' keys.
2836 + */
2837 + public static function products_tags_query( $search_query = null ) {
2838 + if ( ! is_admin() ) {
2839 + return [];
2840 + }
2527 2841
2842 + $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2843 +
2844 + if ( isset( self::$cache[ $cache_key ] ) ) {
2845 + return self::$cache[ $cache_key ];
2846 + }
2847 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2848 + if ( ! $results ) {
2849 + global $wpdb;
2850 + $sql = "SELECT t.term_id, t.name
2851 + FROM {$wpdb->terms} t
2852 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2853 + WHERE tt.taxonomy = 'product_tag'";
2854 +
2855 + if ( $search_query ) {
2856 + $sql .= ' AND t.name LIKE %s';
2857 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2858 + } else {
2859 + $prepared_sql = $sql; // No need for placeholders.
2860 + }
2861 +
2862 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2863 + // Cache the results in the object cache for future use.
2864 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2865 + Cache::set_data_cache_key( $cache_key );
2866 + }
2867 +
2868 + $cats = [];
2869 + if ( ! is_array( $results ) && ! count( $results ) ) {
2870 + return $cats;
2871 + }
2872 + foreach ( $results as $row ) {
2873 + $category_id = $row->term_id;
2874 + $category_title = $row->name;
2875 +
2876 + $cats[] = [
2877 + 'value' => $category_id,
2878 + 'label' => $category_title,
2879 + ];
2880 + }
2881 +
2882 + // Cache the results in a static variable for the next call.
2883 + self::$cache[ $cache_key ] = $cats;
2884 + return $cats;
2885 + }
2886 +
2528 2887 /**
2529 2888 * @param string $search_query Search query.
2530 2889 *
2531 2890 * @return array
@@ -2583,8 +2942,9 @@
2583 2942 /**
2584 2943 * Get Post Types.
2585 2944 *
2586 2945 * @param string $search_query Search query.
2946 + * @param array $args Arguments.
2587 2947 *
2588 2948 * @return array
2589 2949 */
2590 2950 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2595,9 +2955,8 @@
2595 2955 'post_type' => 'any',
2596 2956 'posts_per_page' => 20,
2597 2957 'orderby' => 'ID',
2598 2958 'order' => 'DESC',
2599 - // 'suppress_filters' => false, // WPML Support, Remove Others Languages.
2600 2959 ]
2601 2960 );
2602 2961
2603 2962 if ( 'archive' !== $args['post_type'] ) {
@@ -2608,11 +2967,12 @@
2608 2967 $query_results = get_posts( $args );
2609 2968 foreach ( $query_results as $post ) {
2610 2969 $posts[] = [
2611 2970 'value' => $post->ID,
2612 - 'label' => $post->post_title . ' ( ID ' . $post->ID . ')',
2971 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2613 2972 ];
2614 2973 }
2974 +
2615 2975 if ( $search_query ) {
2616 2976 if ( strpos( '-error 404', $search_query ) ) {
2617 2977 $posts[] = [
2618 2978 'value' => 'error',
@@ -2619,12 +2979,14 @@
2619 2979 'label' => __( '404 Error', 'shopbuilder' ),
2620 2980 ];
2621 2981 }
2622 2982 } else {
2623 - $posts[] = [
2624 - 'value' => 'error',
2625 - 'label' => __( '404 Error', 'shopbuilder' ),
2626 - ];
2983 + if ( 'page' === $args['post_type'] ) {
2984 + $posts[] = [
2985 + 'value' => 'error',
2986 + 'label' => __( '404 Error', 'shopbuilder' ),
2987 + ];
2988 + }
2627 2989 }
2628 2990 } else {
2629 2991 $posts = self::get_registered_post_types( $search_query );
2630 2992 }
@@ -2652,9 +3014,9 @@
2652 3014 public static function renderView( $viewName, $args = [], $return = false ) {
2653 3015 $viewName = str_replace( '.', '/', $viewName );
2654 3016
2655 3017 if ( ! empty( $args ) && is_array( $args ) ) {
2656 - extract( $args );
3018 + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
2657 3019 }
2658 3020
2659 3021 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2660 3022
@@ -2676,9 +3038,9 @@
2676 3038
2677 3039 /**
2678 3040 * Best selling product query.
2679 3041 *
2680 - * @param int $minimum_sale Minimum sale/
3042 + * @param int $minimum_sale Minimum sale.
2681 3043 * @param int $limit Total limit.
2682 3044 *
2683 3045 * @return array|mixed
2684 3046 */
@@ -2736,12 +3098,14 @@
2736 3098
2737 3099 if ( isset( self::$cache[ $cache_key ] ) ) {
2738 3100 return self::$cache[ $cache_key ];
2739 3101 }
3102 + $date_created = $product->get_date_created();
3103 + if ( ! $date_created ) {
3104 + return false;
3105 + }
3106 + $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
2740 3107
2741 - // Get the product publish date.
2742 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2743 -
2744 3108 // Calculate the difference in days.
2745 3109 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2746 3110
2747 3111 // Check if the product is considered new.
@@ -2769,12 +3133,9 @@
2769 3133
2770 3134 /**
2771 3135 * Checks if a feature is disabled for guest users based on the provided option.
2772 3136 *
2773 - * @param string $option The option to retrieve from the item.
2774 - * @param mixed $default The default value if the option is not found.
2775 - *
2776 - * @return bool
3137 + * @return void
2777 3138 */
2778 3139 public static function woocommerce_output_all_notices() {
2779 3140 if ( function_exists( 'wc_print_notices' ) ) :
2780 3141 echo '<div class="rtsb-notice">';
@@ -2876,8 +3237,68 @@
2876 3237 ];
2877 3238 }
2878 3239
2879 3240 /**
3241 + * Retrieve an array of custom icons with their HTML representation.
3242 + *
3243 + * @return array
3244 + */
3245 + public static function get_icons() {
3246 + $icon = self::get_custom_icon_names();
3247 + $icons_array = [];
3248 + foreach ( $icon as $value ) {
3249 + $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3250 + }
3251 + return $icons_array;
3252 + }
3253 +
3254 + /**
3255 + * Generates HTML markup for displaying an endpoint icon.
3256 + *
3257 + * @param array $data The endpoint data containing icon information.
3258 + * @param boolean $wrapper Whether to wrap the icon in a span element.
3259 + *
3260 + * @return string
3261 + */
3262 + public static function get_icon_html( $data, $wrapper = true ) {
3263 + if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3264 + return '';
3265 + }
3266 + $endpoint = '';
3267 + $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3268 +
3269 + if ( 'select_icon' === $data['icon_source'] ) {
3270 + $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3271 + } else {
3272 + $icon_html = '';
3273 + $icon_url = $data['image_icon']['source'] ?? '';
3274 + $icon_id = $data['image_icon']['id'] ?? 0;
3275 + $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3276 +
3277 + if ( 'svg' === $extension ) {
3278 + $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3279 + $is_svg = strpos( $content, '<svg' );
3280 +
3281 + if ( false !== $is_svg ) {
3282 + $icon_html = substr( $content, $is_svg );
3283 + }
3284 + } else {
3285 + $attr = [
3286 + 'class' => 'rtsb-icon-img',
3287 + 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3288 + ];
3289 +
3290 + $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3291 + }
3292 +
3293 + $html .= $icon_html;
3294 + }
3295 +
3296 + $html .= $wrapper ? '</span>' : '';
3297 +
3298 + return $html;
3299 + }
3300 + /**
2880 3301 * Flushes rewrite rules.
2881 3302 *
2882 3303 * @return void
2883 3304 */
@@ -2917,12 +3338,18 @@
2917 3338
2918 3339 /**
2919 3340 * Social Share Platforms.
2920 3341 *
2921 - * @return mixed|null
3342 + * @param string $section_id Section ID.
3343 + * @param string $block_id Block ID.
3344 + * @param string $option_key Option key.
3345 + * @param string $compare_key Compare key.
3346 + * @param string $compare_value Compare value.
3347 + * @param array $values Values.
3348 + *
3349 + * @return void
2922 3350 */
2923 3351 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
2924 - // $options = self::get_options( $section_id, $block_id );
2925 3352 $modules = DataModel::source()->get_option( $section_id, [], false );
2926 3353 if ( empty( $modules[ $block_id ] ) ) {
2927 3354 return;
2928 3355 }
@@ -2960,6 +3387,675 @@
2960 3387 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
2961 3388 }
2962 3389 );
2963 3390 return $ids;
3391 + }
3392 +
3393 + /**
3394 + * Generate and cache dynamic CSS styles.
3395 + *
3396 + * @param array $options CSS options to apply (e.g. padding, margin).
3397 + * @param string $cache_key Cache key for the CSS.
3398 + * @param array $css_properties An array of CSS properties with selectors and properties.
3399 + *
3400 + * @return void
3401 + */
3402 + public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3403 + $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3404 + $style_handle = self::optimized_handle( 'rtsb-frontend' );
3405 +
3406 + if ( false !== $cached_css ) {
3407 + wp_add_inline_style( $style_handle, $cached_css );
3408 + return;
3409 + }
3410 +
3411 + $css_rules = [];
3412 + $grouped_css_rules = [];
3413 +
3414 + foreach ( $css_properties as $option => $details ) {
3415 + if ( ! empty( $options[ $option ] ) ) {
3416 + $selector = $details['selector'] ?? '';
3417 + $property = $details['property'] ?? '';
3418 + $unit = $details['unit'] ?? '';
3419 + $value = $options[ $option ];
3420 +
3421 + if ( empty( $grouped_css_rules[ $selector ] ) ) {
3422 + $grouped_css_rules[ $selector ] = [];
3423 + }
3424 +
3425 + if ( is_array( $property ) ) {
3426 + foreach ( $property as $prop ) {
3427 + $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3428 + }
3429 + } else {
3430 + $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3431 + }
3432 + }
3433 + }
3434 +
3435 + foreach ( $grouped_css_rules as $selector => $properties ) {
3436 + $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3437 + }
3438 +
3439 + $dynamic_css = implode( ' ', $css_rules );
3440 +
3441 + wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3442 + Cache::set_data_cache_key( $cache_key );
3443 +
3444 + if ( ! empty( $dynamic_css ) ) {
3445 + wp_add_inline_style( $style_handle, $dynamic_css );
3446 + }
3447 + }
3448 +
3449 + /**
3450 + * Pro version notice.
3451 + *
3452 + * @param string $ver Pro Version.
3453 + * @param string $tab Tab.
3454 + * @param string $name Name.
3455 + * @param bool $enable_free_Link Enable free link.
3456 + *
3457 + * @return array[]
3458 + */
3459 + public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3460 + return [
3461 + 'version_check' => [
3462 + 'id' => 'version_check',
3463 + 'type' => 'title',
3464 + 'label' => sprintf(
3465 + /* translators: 1: required version, 2: link to the Plugins page */
3466 + esc_html__(
3467 + 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3468 + 'shopbuilder'
3469 + ),
3470 + $name,
3471 + sprintf(
3472 + '<br /><u><b>%s</b></u>',
3473 + esc_html( $ver ?? '1.5.0' ) . ' or higher'
3474 + ),
3475 + $enable_free_Link
3476 + ? sprintf(
3477 + '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3478 + esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3479 + esc_url( admin_url( 'plugins.php' ) ),
3480 + esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3481 + esc_html__( 'from the Plugins page', 'shopbuilder' )
3482 + )
3483 + : ''
3484 + ),
3485 + 'tab' => $tab,
3486 + 'customClass' => 'checkout-notice',
3487 + ],
3488 + ];
3489 + }
3490 +
3491 + /**
3492 + * Get product gallery ids.
3493 + *
3494 + * @param object $product Product object.
3495 + *
3496 + * @return mixed
3497 + */
3498 + public static function get_cached_gallery_ids( $product ) {
3499 + $product_id = $product->get_id();
3500 + $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3501 +
3502 + if ( isset( self::$cache[ $cache_key ] ) ) {
3503 + return self::$cache[ $cache_key ];
3504 + }
3505 +
3506 + $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3507 +
3508 + if ( false !== $cached_result ) {
3509 + self::$cache[ $cache_key ] = $cached_result;
3510 +
3511 + return $cached_result;
3512 + }
3513 +
3514 + $gallery_ids = $product->get_gallery_image_ids();
3515 + self::$cache[ $cache_key ] = $gallery_ids;
3516 +
3517 + wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3518 + Cache::set_data_cache_key( $cache_key );
3519 +
3520 + return $gallery_ids;
3521 + }
3522 +
3523 + /**
3524 + * Check if optimization settings are enabled.
3525 + *
3526 + * @return bool
3527 + */
3528 + public static function is_optimization_enabled() {
3529 + $has_pro = rtsb()->has_pro();
3530 + $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3531 +
3532 + if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3533 + return false;
3534 + }
3535 +
3536 + $generalList = GeneralList::instance()->get_data();
3537 +
3538 + return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3539 + }
3540 +
3541 + /**
3542 + * Get the optimized handle.
3543 + *
3544 + * @param string $handle Base handle used for script/style IDs.
3545 + *
3546 + * @return string
3547 + */
3548 + public static function optimized_handle( $handle ) {
3549 + $use_optimization = self::is_optimization_enabled();
3550 +
3551 + if ( $use_optimization ) {
3552 + $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3553 + }
3554 +
3555 + return $handle;
3556 + }
3557 +
3558 + /**
3559 + * Get the optimized handle by context.
3560 + *
3561 + * @return string
3562 + */
3563 + public static function get_optimized_handle_by_context() {
3564 + $context = self::detect_context();
3565 +
3566 + if ( 'account' === $context ) {
3567 + return 'rtsb-bundled-global';
3568 + }
3569 +
3570 + return "rtsb-bundled-$context";
3571 + }
3572 +
3573 + /**
3574 + * Check if Elementor page.
3575 + *
3576 + * @param int $post_id Post ID.
3577 + *
3578 + * @return bool
3579 + */
3580 + public static function is_elementor_page( $post_id = null ) {
3581 + if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3582 + return false;
3583 + }
3584 +
3585 + $post_id = $post_id ?: get_the_ID();
3586 +
3587 + if ( ! $post_id ) {
3588 + return true;
3589 + }
3590 +
3591 + return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3592 + }
3593 +
3594 + /**
3595 + * Check if shop or archive.
3596 + *
3597 + * @return bool
3598 + */
3599 + public static function is_shop_or_archive() {
3600 + return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3601 + }
3602 +
3603 + /**
3604 + * Detect context.
3605 + *
3606 + * @return string
3607 + */
3608 + public static function detect_context() {
3609 + switch ( true ) {
3610 + case is_product() || BuilderFns::is_product():
3611 + $context = 'product';
3612 + break;
3613 +
3614 + case is_cart() || BuilderFns::is_cart():
3615 + $context = 'cart';
3616 + break;
3617 +
3618 + case is_checkout() || BuilderFns::is_checkout():
3619 + $context = 'checkout';
3620 + break;
3621 +
3622 + case self::is_shop_or_archive():
3623 + $context = 'shop';
3624 + break;
3625 +
3626 + default:
3627 + $context = 'global';
3628 + break;
3629 + }
3630 +
3631 + return apply_filters( 'rtsb/optimizer/context_detect', $context );
3632 + }
3633 +
3634 + /**
3635 + * Enqueue optimized assets.
3636 + *
3637 + * @return void
3638 + */
3639 + public static function enqueue_optimized_assets() {
3640 + $asset_registry = AssetRegistry::instance();
3641 + $bundled_assets = $asset_registry->get_bundled_assets();
3642 + $context = self::detect_context();
3643 + $contextual_loading = self::is_contextual_loading();
3644 + $theme_handle = self::find_theme_stylesheet_handle();
3645 +
3646 + if ( $contextual_loading ) {
3647 + // Enqueue JS.
3648 + if ( ! empty( $bundled_assets['js'] ) ) {
3649 + $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3650 +
3651 + if ( $js_context ) {
3652 + wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3653 + }
3654 + }
3655 +
3656 + // Enqueue CSS.
3657 + if ( ! empty( $bundled_assets['css'] ) ) {
3658 + $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3659 +
3660 + if ( $css_context ) {
3661 + $handle = $bundled_assets['css'][ $css_context ]['handle'];
3662 +
3663 + wp_enqueue_style( $handle );
3664 +
3665 + if ( ! empty( $theme_handle ) ) {
3666 + self::set_theme_dependency( $theme_handle, $handle );
3667 + }
3668 + }
3669 + }
3670 + } else {
3671 + // Enqueue JS.
3672 + if ( ! empty( $bundled_assets['js'] ) ) {
3673 + wp_enqueue_script( $bundled_assets['js']['handle'] );
3674 + }
3675 +
3676 + // Enqueue CSS.
3677 + if ( ! empty( $bundled_assets['css'] ) ) {
3678 + $handle = $bundled_assets['css']['handle'];
3679 +
3680 + wp_enqueue_style( $handle );
3681 +
3682 + if ( ! empty( $theme_handle ) ) {
3683 + self::set_theme_dependency( $theme_handle, $handle );
3684 + }
3685 + }
3686 + }
3687 + }
3688 +
3689 + /**
3690 + * Find theme stylesheet handle.
3691 + *
3692 + * @return string|false
3693 + */
3694 + private static function find_theme_stylesheet_handle() {
3695 + static $cached_handle = null;
3696 +
3697 + if ( null !== $cached_handle ) {
3698 + return $cached_handle;
3699 + }
3700 +
3701 + global $wp_styles;
3702 +
3703 + if ( ! $wp_styles instanceof WP_Styles ) {
3704 + return false;
3705 + }
3706 +
3707 + $stylesheet_uri = get_stylesheet_uri();
3708 + $theme_directory_uri = get_stylesheet_directory_uri();
3709 +
3710 + $hash = md5( $stylesheet_uri );
3711 + $transient_key = 'rtsb_theme_css_handle_' . $hash;
3712 +
3713 + $transient = get_transient( $transient_key );
3714 +
3715 + if ( false !== $transient ) {
3716 + $cached_handle = $transient;
3717 +
3718 + return $transient;
3719 + }
3720 +
3721 + foreach ( $wp_styles->registered as $handle => $style ) {
3722 + $src = $style->src;
3723 +
3724 + if (
3725 + ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3726 + strpos( $src, 'style.css' ) !== false
3727 + ) {
3728 + set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3729 + Cache::set_transient_cache_key( $transient_key );
3730 + $cached_handle = $handle;
3731 +
3732 + return $handle;
3733 + }
3734 + }
3735 +
3736 + $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3737 +
3738 + if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3739 + set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3740 + Cache::set_transient_cache_key( $transient_key );
3741 + $cached_handle = $filtered_handle;
3742 +
3743 + return $filtered_handle;
3744 + }
3745 +
3746 + set_transient( $transient_key, false, DAY_IN_SECONDS );
3747 + Cache::set_transient_cache_key( $transient_key );
3748 + $cached_handle = false;
3749 +
3750 + return false;
3751 + }
3752 +
3753 + /**
3754 + * Set theme dependency.
3755 + *
3756 + * @param string $theme_handle Theme handle.
3757 + * @param string $our_handle Our handle.
3758 + *
3759 + * @return void
3760 + */
3761 + private static function set_theme_dependency( $theme_handle, $our_handle ) {
3762 + global $wp_styles;
3763 +
3764 + if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3765 + return;
3766 + }
3767 +
3768 + $theme_style = $wp_styles->registered[ $theme_handle ];
3769 +
3770 + // Add our handle as a dependency if it's not already there.
3771 + if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3772 + $theme_style->deps[] = $our_handle;
3773 + }
3774 + }
3775 +
3776 + /**
3777 + * Check if Elementor scripts should be loaded.
3778 + *
3779 + * @return bool
3780 + */
3781 + public static function should_load_elementor_scripts() {
3782 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3783 +
3784 + $enable_optimization = $data['enable_optimization'] ?? 'on';
3785 + $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3786 +
3787 + if ( 'on' !== $enable_optimization ) {
3788 + return true;
3789 + }
3790 +
3791 + return 'on' === $load_elementor_scripts;
3792 + }
3793 +
3794 + /**
3795 + * Locate asset.
3796 + *
3797 + * @param string $relative_path Relative path.
3798 + *
3799 + * @return string|null
3800 + */
3801 + public static function locate_asset( $relative_path ) {
3802 + $free_context = rtsb();
3803 + $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3804 +
3805 + $paths = [];
3806 +
3807 + if ( $pro_context ) {
3808 + $paths[] = $pro_context->get_assets_path( $relative_path );
3809 + }
3810 +
3811 + $paths[] = $free_context->get_assets_path( $relative_path );
3812 +
3813 + foreach ( $paths as $path ) {
3814 + if ( file_exists( $path ) ) {
3815 + return $path;
3816 + }
3817 + }
3818 +
3819 + return null;
3820 + }
3821 +
3822 + /**
3823 + * Enqueue module assets.
3824 + *
3825 + * @param string $handle Asset handle.
3826 + * @param string $module_name Module name.
3827 + * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3828 + *
3829 + * @return string
3830 + */
3831 + public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3832 + $use_optimization = self::is_optimization_enabled();
3833 + $handle = self::optimized_handle( $handle );
3834 +
3835 + if ( $use_optimization ) {
3836 + return $handle;
3837 + }
3838 +
3839 + $defaults = [
3840 + 'type' => 'both',
3841 + 'deps' => [ 'jquery', 'rtsb-public' ],
3842 + 'context' => null,
3843 + 'version' => RTSB_VERSION,
3844 + ];
3845 +
3846 + $config = array_merge( $defaults, $options );
3847 + $rtl_suffix = is_rtl() ? '-rtl' : '';
3848 + $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3849 + $context = $config['context'] ?: rtsb();
3850 + $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3851 + $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3852 +
3853 + // Register CSS if enabled.
3854 + if ( $load_css ) {
3855 + wp_register_style(
3856 + $handle,
3857 + $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3858 + [],
3859 + $config['version']
3860 + );
3861 + }
3862 +
3863 + // Register JS if enabled.
3864 + if ( $load_js ) {
3865 + wp_register_script(
3866 + $handle,
3867 + $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3868 + $config['deps'],
3869 + $config['version'],
3870 + true
3871 + );
3872 + }
3873 +
3874 + if ( $load_css ) {
3875 + wp_enqueue_style( $handle );
3876 +
3877 + $theme_handle = self::find_theme_stylesheet_handle();
3878 +
3879 + if ( ! empty( $theme_handle ) ) {
3880 + self::set_theme_dependency( $theme_handle, $handle );
3881 + }
3882 + }
3883 +
3884 + if ( $load_js ) {
3885 + wp_enqueue_script( $handle );
3886 + }
3887 +
3888 + return $handle;
3889 + }
3890 +
3891 + /**
3892 + * Check if contextual loading is enabled.
3893 + *
3894 + * @return bool
3895 + */
3896 + public static function is_contextual_loading() {
3897 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3898 +
3899 + return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3900 + }
3901 +
3902 + /**
3903 + * Get Modules list with cache support.
3904 + *
3905 + * @return array
3906 + */
3907 + public static function get_modules_list() {
3908 + static $cached_modules = null;
3909 +
3910 + if ( null !== $cached_modules ) {
3911 + return $cached_modules;
3912 + }
3913 +
3914 + $cache_key = 'rtsb_module_list';
3915 + $cache_group = 'shopbuilder';
3916 +
3917 + $cached = wp_cache_get( $cache_key, $cache_group );
3918 +
3919 + if ( false !== $cached ) {
3920 + $cached_modules = $cached;
3921 +
3922 + return $cached;
3923 + }
3924 +
3925 + $modules = ModuleList::instance()->get_data();
3926 +
3927 + wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3928 + Cache::set_data_cache_key( $cache_key );
3929 +
3930 + $cached_modules = $modules;
3931 +
3932 + return $modules;
3933 + }
3934 +
3935 + /**
3936 + * Get Elementor widgets list with cache support.
3937 + *
3938 + * @return array
3939 + */
3940 + public static function get_widgets_list() {
3941 + static $cached_widgets = null;
3942 +
3943 + if ( null !== $cached_widgets ) {
3944 + return $cached_widgets;
3945 + }
3946 +
3947 + $cache_key = 'rtsb_elementor_widget_list';
3948 + $cache_group = 'shopbuilder';
3949 +
3950 + $cached = wp_cache_get( $cache_key, $cache_group );
3951 +
3952 + if ( false !== $cached ) {
3953 + $cached_widgets = $cached;
3954 +
3955 + return $cached;
3956 + }
3957 +
3958 + $widgets = ElementList::instance()->get_list();
3959 +
3960 + wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
3961 + Cache::set_data_cache_key( $cache_key );
3962 +
3963 + $cached_widgets = $widgets;
3964 +
3965 + return $widgets;
3966 + }
3967 +
3968 + /**
3969 + * Convert the given price to the active currency.
3970 + *
3971 + * @param float $price The original price.
3972 + * @param Object||null $product Product.
3973 + *
3974 + * @return float
3975 + */
3976 + public static function get_currency_base_price( $price, $product = null ) {
3977 + return apply_filters( 'rtsb/convert/currency/price', $price, $product );
3978 + }
3979 + /**
3980 + * Generate a signed URL with a payload.
3981 + *
3982 + * @param array $payload Associative data to encode.
3983 + * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
3984 + * @param string $key Key name to use in the URL.
3985 + * @return string Signed URL with key parameter.
3986 + */
3987 + public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
3988 + if ( empty( $key ) ) {
3989 + $key = 'key';
3990 + }
3991 + // Convert payload to JSON.
3992 + $data = wp_json_encode( $payload );
3993 + // Create signature.
3994 + $signature = wp_hash( $data );
3995 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
3996 + $encode_key = base64_encode( $data . '::' . $signature );
3997 + // Return full URL with key param.
3998 + return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
3999 + }
4000 +
4001 + /**
4002 + * Decode and verify a signed URL key.
4003 + *
4004 + * @param string $key Encoded key from URL.
4005 + * @return array|false Decoded payload array on success, false on failure.
4006 + */
4007 + public static function decode_signed_key( string $key ) {
4008 + if ( empty( $key ) ) {
4009 + return false;
4010 + }
4011 + $key = rawurldecode( wp_unslash( $key ) );
4012 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
4013 + $raw = base64_decode( sanitize_text_field( $key ) );
4014 + if ( ! $raw ) {
4015 + return false;
4016 + }
4017 + $parts = explode( '::', $raw, 2 );
4018 + if ( count( $parts ) !== 2 ) {
4019 + return false;
4020 + }
4021 + list( $data_json, $signature ) = $parts;
4022 + // Validate signature.
4023 + if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4024 + return false;
4025 + }
4026 + $payload = json_decode( $data_json, true );
4027 + return is_array( $payload ) ? $payload : false;
4028 + }
4029 +
4030 + /**
4031 + * Get Loco Translate MO file path for a plugin.
4032 + *
4033 + * @param string $textdomain Plugin textdomain.
4034 + * @return void|false
4035 + */
4036 + public static function load_loco_textdomain( $textdomain ) {
4037 + if ( ! function_exists( 'loco_plugin_version' ) ) {
4038 + return false;
4039 + }
4040 + $lang = WP_LANG_DIR;
4041 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4042 + if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4043 + $lang = LOCO_LANG_DIR;
4044 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4045 + }
4046 + if ( ! file_exists( $path ) ) {
4047 + if ( 'shopbuilder' === $textdomain ) {
4048 + $plugin_root = dirname( RTSB_FILE );
4049 + } elseif ( 'shopbuilder-pro' === $textdomain ) {
4050 + $plugin_root = dirname( RTSBPRO_FILE );
4051 + } else {
4052 + return false;
4053 + }
4054 + $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4055 + }
4056 + if ( ! file_exists( $path ) ) {
4057 + return false;
4058 + }
4059 + load_textdomain( $textdomain, $path );
2964 4060 }
2965 4061 }