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
← All changes | app/Helpers/Fns.php +1281 -138 2.2.13.2.6 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,10 +36,55 @@
28 36 /**
29 37 * @var array
30 38 */
31 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 + }
32 54
33 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 + // ✅ Step 1: Theme bypass (no license check).
78 + if ( in_array( $current_theme, $allowed_themes, true ) ) {
79 + $cached_result = true;
80 + return true;
81 + }
82 + // ✅ Step 3: Normal license validation
83 + $license_status = rtsbpro()->get_license( 'license_status' );
84 + return ( 'valid' === $license_status );
85 + }
86 + /**
34 87 * Verify nonce.
35 88 *
36 89 * @return bool
37 90 */
@@ -44,19 +97,46 @@
44 97
45 98 return false;
46 99 }
47 100
101 + /**
102 + * Get nonce.
103 + *
104 + * @return string|null
105 + */
106 + public static function enable_loader() {
107 + return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
108 + }
109 + /**
110 + * Get nonce.
111 + *
112 + * @return string|null
113 + */
114 + public static function content_invisible() {
115 + $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
116 + if ( $wp_rocket_compatibility ) {
117 + return true;
118 + }
119 + if ( defined( 'WP_ROCKET_VERSION' ) ) {
120 + return false;
121 + }
122 + return true;
123 + }
124 + /**
125 + * Get nonce.
126 + *
127 + * @return string|null
128 + */
48 129 public static function get_nonce() {
49 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
130 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
50 131 return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
51 132 }
52 133
53 -
54 134 /**
55 - * Set Cookie or Session
135 + * Set Session
56 136 *
57 - * @param $name
58 - * @param $value
137 + * @param string $name Name.
138 + * @param mixed $value Value.
59 139 */
60 140 public static function setSession( $name, $value ) {
61 141 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 142 session_start();
@@ -64,12 +144,13 @@
64 144 } else {
65 145 $_SESSION[ $name ] = $value;
66 146 }
67 147 }
148 +
68 149 /**
69 150 * Get Cookie or Session
70 151 *
71 - * @param $name
152 + * @param string $name Name.
72 153 *
73 154 * @return bool
74 155 */
75 156 public static function getSession( $name ) {
@@ -75,9 +156,9 @@
75 156 public static function getSession( $name ) {
76 157 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
77 158 session_start();
78 159 }
79 - return $_SESSION[ $name ] ?? null;
160 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
80 161 }
81 162 /**
82 163 * Remove Session Variable
83 164 *
@@ -90,12 +171,43 @@
90 171 } else {
91 172 unset( $_SESSION[ $name ] );
92 173 }
93 174 }
94 -
95 175 /**
96 - * @param $plugin_slug
176 + * Gets the current timestamp in WordPress timezone.
97 177 *
178 + * @return int Current timestamp.
179 + */
180 + public static function currentTimestampUTC() {
181 + $wp_timezone = wp_timezone();
182 + $now = new DateTime( 'now', $wp_timezone ); // Current time in WP timezone.
183 + return $now->getTimestamp();
184 + }
185 + /**
186 + * Action.
187 + *
188 + * @param string $action action.
189 + * @return void
190 + */
191 + public static function add_to_scheduled_hook_list( $action ) {
192 + if ( empty( $action ) ) {
193 + return;
194 + }
195 + $schedule = get_option( 'rtsb_cron_schedule_free', [] );
196 + $schedule[] = $action;
197 + update_option( 'rtsb_cron_schedule_free', array_unique( $schedule ) );
198 + }
199 + /**
200 + * @return DB
201 + */
202 + public static function DB() {
203 + return new DB( 'wpdb' );
204 + }
205 + /**
206 + * Check if a plugin is installed.
207 + *
208 + * @param string $plugin_slug Plugin slug.
209 + *
98 210 * @return bool
99 211 */
100 212 public static function check_plugin_installed( $plugin_slug ): bool {
101 213 $installed_plugins = get_plugins();
@@ -103,10 +215,12 @@
103 215 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
104 216 }
105 217
106 218 /**
107 - * @param $plugin_slug
219 + * Check if a plugin is active.
108 220 *
221 + * @param string $plugin_slug Plugin slug.
222 + *
109 223 * @return bool
110 224 */
111 225 public static function check_plugin_active( $plugin_slug ): bool {
112 226 if ( is_plugin_active( $plugin_slug ) ) {
@@ -114,21 +228,71 @@
114 228 }
115 229
116 230 return false;
117 231 }
232 + /**
233 + * Get all user roles.
234 + *
235 + * @return array
236 + */
237 + public static function get_current_user_roles() {
238 + $current_user = wp_get_current_user();
239 + return $current_user->roles;
240 + }
241 + /**
242 + * Get all user roles.
243 + *
244 + * @return array|false|mixed
245 + */
246 + public static function get_all_user_roles() {
247 + $cache_key = 'rtsb_get_user_roles';
248 + $roles = wp_cache_get( $cache_key, 'shopbuilder' );
118 249
250 + if ( empty( $roles ) ) {
251 + if ( ! function_exists( 'get_editable_roles' ) ) {
252 + require_once ABSPATH . 'wp-admin/includes/user.php';
253 + }
254 +
255 + $user_roles = \get_editable_roles();
256 + $roles = [];
257 +
258 + foreach ( $user_roles as $key => $role ) {
259 + if ( empty( $role['capabilities'] ) ) {
260 + continue;
261 + }
262 + $roles[ $key ] = $role['name'];
263 + }
264 + }
265 + wp_cache_set( $cache_key, $roles, 'shopbuilder' );
266 + Cache::set_data_cache_key( $cache_key );
267 + return $roles;
268 + }
269 +
119 270 /**
120 - * @param $data
271 + * Stripslashes value.
121 272 *
273 + * @param mixed $data Data.
274 + *
122 275 * @return mixed|string
123 276 */
124 277 public static function stripslashes_value( $data ) {
125 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
126 - return stripslashes( $data );
127 - } elseif ( is_array( $data ) ) {
278 + if ( is_array( $data ) ) {
128 279 foreach ( $data as $key => $value ) {
129 280 $data[ $key ] = self::stripslashes_value( $value );
130 281 }
282 + } elseif ( is_string( $data ) ) {
283 + $trimmed = trim( $data );
284 + // Try to decode JSON.
285 + $json = json_decode( $trimmed, true );
286 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
287 + // Recursively stripslashes on decoded array.
288 + $json = self::stripslashes_value( $json );
289 + return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
290 + }
291 + // Not valid JSON, just stripslashes if needed.
292 + if ( strpos( $data, '\\' ) !== false ) {
293 + return stripslashes( $data );
294 + }
131 295 }
132 296
133 297 return $data;
134 298 }
@@ -133,10 +297,12 @@
133 297 return $data;
134 298 }
135 299
136 300 /**
137 - * @param $string
301 + * Multiselect settings field value
138 302 *
303 + * @param string $string String.
304 + *
139 305 * @return array
140 306 */
141 307 public static function multiselect_settings_field_value( $string ) {
142 308
@@ -156,8 +322,10 @@
156 322 return $values;
157 323 }
158 324
159 325 /**
326 + * Check if is block theme
327 + *
160 328 * @return bool
161 329 */
162 330 public static function check_is_block_theme(): bool {
163 331 global $wp_version;
@@ -165,8 +333,10 @@
165 333 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
166 334 }
167 335
168 336 /**
337 + * Locate template
338 + *
169 339 * @param string $template_name Template name.
170 340 * @param string $template_path Template path. (default: '').
171 341 * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
172 342 *
@@ -197,12 +367,12 @@
197 367 )
198 368 );
199 369
200 370 if ( ! $located ) {
201 - if ( file_exists( $plugin_path ) ) {
371 + if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
372 + return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
373 + } elseif ( file_exists( $plugin_path ) ) {
202 374 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 375 }
206 376 }
207 377
208 378 /**
@@ -239,9 +409,9 @@
239 409 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
240 410 *
241 411 * @return false|string|void
242 412 */
243 - public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
413 + public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
244 414 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
245 415 $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
246 416 if ( ! $located ) {
247 417 $located = self::locate_template( $template_name, $template_path, $plugin_path );
@@ -260,9 +430,9 @@
260 430 }
261 431
262 432 if ( ! empty( $args ) && is_array( $args ) ) {
263 433 $atts = $args;
264 - extract( $args ); // @codingStandardsIgnoreLine
434 + extract( $args ); // @codingStandardsIgnoreLine
265 435 }
266 436
267 437 // Allow 3rd party plugin filter template file from their plugin.
268 438 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -377,9 +547,9 @@
377 547 // Return the Builder Template id.
378 548
379 549 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
380 550 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
381 - if ( $product_id ) {
551 + if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
382 552 return $product_id;
383 553 }
384 554 }
385 555
@@ -386,9 +556,9 @@
386 556 global $wpdb;
387 557 $cache_key = 'rtsb_prepared_product_id';
388 558 $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
389 559 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
390 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
560 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
391 561 $_post_id = $wpdb->get_var(
392 562 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
393 563 );
394 564 wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
@@ -406,9 +576,8 @@
406 576 public static function get_product() {
407 577 global $product;
408 578
409 579 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
410 - // do_action( 'rtsb_before_product_template_render' );.
411 580 return $product;
412 581 }
413 582 $cache_key = 'prepared_product_for_preview';
414 583 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -429,8 +598,9 @@
429 598 *
430 599 * @return void
431 600 */
432 601 public static function doing_it_wrong( $function, $message, $version ) {
602 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
433 603 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
434 604 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
435 605 }
436 606
@@ -448,8 +618,15 @@
448 618
449 619 return $pages;
450 620 }
451 621
622 + /**
623 + * Get section items
624 + *
625 + * @param string $section_id Section ID.
626 + *
627 + * @return array
628 + */
452 629 public static function get_section_items( $section_id ) {
453 630 if ( ! $section_id ) {
454 631 return [];
455 632 }
@@ -456,8 +633,16 @@
456 633
457 634 return DataModel::source()->get_option( $section_id, [] );
458 635 }
459 636
637 + /**
638 + * Get options items
639 + *
640 + * @param string $section_id Section ID.
641 + * @param string $item_id Item ID.
642 + *
643 + * @return array
644 + */
460 645 public static function get_options( $section_id, $item_id ) {
461 646 if ( ! $section_id || ! $item_id ) {
462 647 return [];
463 648 }
@@ -468,11 +653,11 @@
468 653
469 654 /**
470 655 * Get options value with default value if options doesn't exist.
471 656 *
472 - * @param $group
473 - * @param $option_key
474 - * @param $default_value
657 + * @param array $group Group.
658 + * @param string $option_key Option key.
659 + * @param string $default_value Default value.
475 660 *
476 661 * @return mixed|string
477 662 */
478 663 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -484,32 +669,34 @@
484 669 return $group[ $option_key ];
485 670 }
486 671
487 672 /**
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
673 + * Get option.
493 674 *
675 + * @param string $section_id Section ID.
676 + * @param string $item_id Item ID.
677 + * @param string $option_id Option ID.
678 + * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
679 + * @param null $type checkbox, multi_checkbox, number.
680 + *
494 681 * @return bool|int|mixed|null
495 682 */
496 683 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
497 684 $options = self::get_options( $section_id, $item_id );
498 685
499 - if ( $type === 'checkbox' ) {
686 + if ( 'checkbox' === $type ) {
500 687 if ( isset( $options[ $option_id ] ) ) {
501 - return $options[ $option_id ] === 'on';
688 + return 'on' === $options[ $option_id ];
502 689 }
503 690
504 691 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' ) {
692 + } elseif ( 'multi_checkbox' === $type ) {
693 + return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
694 + } elseif ( 'number' === $type ) {
508 695 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
509 696 }
510 697
511 - return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
698 + return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
512 699 }
513 700
514 701
515 702 /**
@@ -564,9 +751,9 @@
564 751
565 752 if ( strlen( $page_content ) > 0 ) {
566 753 // Search for an existing page with the specified page content (typically a shortcode).
567 754 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
568 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
755 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
569 756 $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 757 } else {
571 758 // Search for an existing page with the specified page slug.
572 759 $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 +823,13 @@
636 823
637 824 return $page_id;
638 825 }
639 826
827 + /**
828 + * Get allowed HTML tags.
829 + *
830 + * @return array
831 + */
640 832 public static function get_kses_array() {
641 833 return [
642 834 'a' => [
643 835 'class' => [],
@@ -736,13 +928,14 @@
736 928 ];
737 929 }
738 930
739 931
740 - /*
932 + /**
741 933 * Escape output of wishlist icon
742 934 *
743 935 * @param string $data Data to escape.
744 - * @return string Escaped data
936 + *
937 + * @return void
745 938 */
746 939 public static function print_icon( $data ) {
747 940 /**
748 941 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -826,9 +1019,9 @@
826 1019 * @return mixed
827 1020 */
828 1021 public static function allowedHtml( $level = 'basic' ) {
829 1022 $allowed_html = [];
830 - // TODO:: Need Optimize.
1023 +
831 1024 switch ( $level ) {
832 1025 case 'basic':
833 1026 $allowed_html = [
834 1027 'b' => [
@@ -968,13 +1161,15 @@
968 1161 return $allowed_html;
969 1162 }
970 1163
971 1164 /**
972 - * Safe print a validated HTML tag.
1165 + * Safe get a validated HTML tag.
973 1166 *
974 - * @param string $tag
1167 + * @param string $tag HTML tag.
1168 + *
1169 + * @return string
975 1170 */
976 - public static function print_validated_html_tag( $tag ) {
1171 + public static function get_validated_html_tag( $tag ) {
977 1172 $allowed_html_wrapper_tags = [
978 1173 'a',
979 1174 'article',
980 1175 'aside',
@@ -994,12 +1189,23 @@
994 1189 'section',
995 1190 'span',
996 1191 ];
997 1192
998 - self::print_html( in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div' );
1193 + return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
999 1194 }
1000 1195
1001 1196 /**
1197 + * Safe print a validated HTML tag.
1198 + *
1199 + * @param string $tag HTML tag.
1200 + *
1201 + * @return void
1202 + */
1203 + public static function print_validated_html_tag( $tag ) {
1204 + self::print_html( self::get_validated_html_tag( $tag ) );
1205 + }
1206 +
1207 + /**
1002 1208 * Insert Some array element
1003 1209 *
1004 1210 * @param [type] $key The elements will insert nearby this key.
1005 1211 * @param [type] $main_array Original array.
@@ -1050,9 +1256,9 @@
1050 1256
1051 1257 $imgSize = [];
1052 1258
1053 1259 foreach ( $sizes as $key => $img ) {
1054 - $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1260 + $imgSize[ $key ] = esc_html( $key ) . " ({$img['width']}*{$img['height']})";
1055 1261 }
1056 1262
1057 1263 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1058 1264 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
@@ -1062,11 +1268,17 @@
1062 1268
1063 1269 /**
1064 1270 * Free Layouts
1065 1271 *
1272 + * @param string $layout Layout.
1273 + *
1066 1274 * @return array
1067 1275 */
1068 - public static function free_layouts() {
1276 + public static function free_layouts( $layout = '' ) {
1277 + if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1278 + return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1279 + }
1280 +
1069 1281 $layouts = [
1070 1282 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1071 1283 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1072 1284 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -1120,8 +1332,25 @@
1120 1332 }
1121 1333 self::$cache[ $cache_key ] = $terms;
1122 1334 return $terms;
1123 1335 }
1336 + /**
1337 + * Get all product attribute taxonomy by id
1338 + *
1339 + * @param array $term_ids Term id.
1340 + *
1341 + * @return array
1342 + */
1343 + public static function tax_filter_attr_taxonomy( $term_ids ) {
1344 + $taxonomies = [];
1345 + foreach ( $term_ids as $id ) {
1346 + $term = get_term( $id );
1347 + if ( ! is_wp_error( $term ) && $term ) {
1348 + $taxonomies[] = $term->taxonomy;
1349 + }
1350 + }
1351 + return array_unique( $taxonomies );
1352 + }
1124 1353
1125 1354 /**
1126 1355 * Get all terms by attributes
1127 1356 *
@@ -1233,9 +1462,8 @@
1233 1462 if ( ! is_admin() ) {
1234 1463 return [];
1235 1464 }
1236 1465
1237 - // TODO:: Return Slug always true for all settings.
1238 1466 $term_list = [];
1239 1467 $args = [
1240 1468 'taxonomy' => $taxonomy,
1241 1469 'hide_empty' => false,
@@ -1305,9 +1533,9 @@
1305 1533
1306 1534 if ( $args['show_rating_count'] ) {
1307 1535 $count .= '<div class="rtsb-count">';
1308 1536 $count .= sprintf(
1309 - /* translators: %s is the number of reviews */
1537 + /* translators: %s is the number of reviews */
1310 1538 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1311 1539 $rating_count
1312 1540 );
1313 1541 $count .= '</div>';
@@ -1332,8 +1560,13 @@
1332 1560
1333 1561 self::print_html( $html, true );
1334 1562 }
1335 1563
1564 + /**
1565 + * Get product simple rating.
1566 + *
1567 + * @return void
1568 + */
1336 1569 public static function get_product_simple_rating_html() {
1337 1570 global $product;
1338 1571 $html = null;
1339 1572 $rating_count = $product->get_rating_count();
@@ -1445,9 +1678,42 @@
1445 1678
1446 1679 <?php
1447 1680 self::print_html( ob_get_clean() );
1448 1681 }
1682 + /**
1683 + * Brands HTML
1684 + *
1685 + * @param int $id Product ID.
1686 + * @param string $class Custom class.
1687 + *
1688 + * @return void|string
1689 + */
1690 + public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1691 + $terms = get_the_terms( $id, 'product_brand' );
1692 + if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1693 + return '';
1694 + }
1695 + ob_start();
1696 + ?>
1449 1697
1698 + <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1699 + <?php
1700 + $brand_list = get_the_term_list(
1701 + $id,
1702 + 'product_brand',
1703 + '<li class="rtsb-brand-list-item">',
1704 + '</li><li class="rtsb-brand-list-item">',
1705 + '</li>'
1706 + );
1707 +
1708 + self::print_html( $brand_list );
1709 + ?>
1710 + </ul>
1711 +
1712 + <?php
1713 + self::print_html( ob_get_clean() );
1714 + }
1715 +
1450 1716 /**
1451 1717 * Get Featured Image HTML.
1452 1718 *
1453 1719 * @param string $type Image type.
@@ -1550,9 +1816,9 @@
1550 1816
1551 1817 if ( ! $img_html ) {
1552 1818 $hwstring = image_hwstring( 160, 160 );
1553 1819 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1554 - $attr['class'] = 'default-img';
1820 + $attr['class'] = 'default-img ' . $hover_class;
1555 1821 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1556 1822 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1557 1823 $img_html = rtrim( "<img $hwstring" );
1558 1824
@@ -1643,9 +1909,9 @@
1643 1909 if ( empty( $control['value'] ) ) {
1644 1910 return '';
1645 1911 }
1646 1912 if ( is_array( $control['value'] ) ) {
1647 - $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) );
1913 + $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1648 1914 } else {
1649 1915 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1650 1916 }
1651 1917 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -1681,9 +1947,13 @@
1681 1947 public static function get_product_swatches( $type ) {
1682 1948 ?>
1683 1949 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1684 1950 <?php
1685 - do_action( 'rtwpvs_show_archive_variation' );
1951 + if ( class_exists( 'Rtwpvsp' ) ) {
1952 + do_action( 'rtwpvs_show_archive_variation' );
1953 + } else {
1954 + do_action( 'rtsb/vs/showcase/variation' );
1955 + }
1686 1956 ?>
1687 1957 </div>
1688 1958 <?php
1689 1959 }
@@ -1780,47 +2050,35 @@
1780 2050 public static function calculate_sale_percentage( $product = null ) {
1781 2051 if ( is_null( $product ) ) {
1782 2052 global $product;
1783 2053 }
1784 -
1785 2054 if ( ! $product instanceof WC_Product ) {
1786 2055 return '';
1787 2056 }
1788 -
1789 2057 if ( ! $product->is_on_sale() ) {
1790 2058 return '';
1791 2059 }
1792 -
1793 - $percentage = null;
1794 - $max_percentage = '';
1795 -
2060 + $max_percentage = 0;
1796 2061 if ( $product->is_type( 'simple' ) ) {
1797 - $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
2062 + $regular_price = (float) $product->get_regular_price();
2063 + $sale_price = (float) $product->get_sale_price();
2064 + if ( $regular_price > 0 && $sale_price > 0 ) {
2065 + $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2066 + }
1798 2067 } elseif ( $product->is_type( 'variable' ) ) {
1799 - $max_percentage = 0;
2068 + $prices = $product->get_variation_prices( true );
2069 + if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
2070 + foreach ( $prices['regular_price'] as $key => $regular_price ) {
2071 + $sale_price = $prices['sale_price'][ $key ];
1800 2072
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;
2073 + if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
2074 + $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2075 + $max_percentage = max( $max_percentage, $percentage );
2076 + }
1810 2077 }
1811 -
1812 - if ( $percentage > $max_percentage ) {
1813 - $max_percentage = $percentage;
1814 - }
1815 2078 }
1816 2079 }
1817 -
1818 - if ( $max_percentage > 0 ) {
1819 - return round( $max_percentage );
1820 - }
1821 -
1822 - return 0;
2080 + return $max_percentage > 0 ? round( $max_percentage ) : 0;
1823 2081 }
1824 2082
1825 2083 /**
1826 2084 * Pagination
@@ -1837,15 +2095,15 @@
1837 2095
1838 2096 global $paged;
1839 2097
1840 2098 if ( is_front_page() ) {
1841 - $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
2099 + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1842 2100 } else {
1843 - $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
2101 + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1844 2102 }
1845 2103
1846 2104 if ( empty( $paged ) ) {
1847 - $paged = 1;
2105 + $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1848 2106 }
1849 2107
1850 2108 if ( '' === $pages ) {
1851 2109 global $wp_query;
@@ -2003,10 +2261,8 @@
2003 2261 } else {
2004 2262 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2005 2263 }
2006 2264
2007 - $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
2008 -
2009 2265 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2010 2266 $html .= $cart_html;
2011 2267 } else {
2012 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -2011,9 +2267,11 @@
2011 2267 } else {
2012 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2013 2269 }
2014 2270
2015 - $html .= '</' . esc_attr( $wrapper ) . '>';
2271 + if ( ! empty( $html ) ) {
2272 + $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2273 + }
2016 2274
2017 2275 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2018 2276 }
2019 2277
@@ -2114,9 +2372,9 @@
2114 2372 $link['social_network'] = 'Facebook';
2115 2373 $link['social_action'] = 'Share';
2116 2374 break;
2117 2375 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'] );
2376 + $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 2377 $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 2378 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2121 2379 $link['social_network'] = 'Twitter';
2122 2380 $link['social_action'] = 'Tweet';
@@ -2150,9 +2408,9 @@
2150 2408 $link['social_action'] = 'Share';
2151 2409 break;
2152 2410 case 'reddit':
2153 2411 $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>';
2412 + $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 2413 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2156 2414 $link['social_network'] = 'Reddit';
2157 2415 $link['social_action'] = 'Share';
2158 2416 break;
@@ -2240,27 +2498,61 @@
2240 2498
2241 2499 /**
2242 2500 * Social Share Platforms.
2243 2501 *
2244 - * @return mixed|null
2502 + * @param string $module Module.
2503 + *
2504 + * @return true|void
2245 2505 */
2246 2506 public static function is_module_active( $module ) {
2247 - $modulelist = ModuleList::instance()->get_data();
2507 + $modulelist = self::get_modules_list();
2508 +
2248 2509 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2249 2510 return true;
2250 2511 }
2251 2512 }
2513 + /**
2514 + * Checks if catalog pro is active
2515 + *
2516 + * @return boolean
2517 + */
2518 + public static function is_catalog_mode() {
2519 + return function_exists( 'rtsbpro' ) && self::is_module_active( 'catalog_mode' );
2520 + }
2521 + /**
2522 + * Checks if back in stock notifier is active
2523 + *
2524 + * @return boolean
2525 + */
2526 + public static function is_back_in_stock_notifier_enable() {
2527 + return function_exists( 'rtsbpro' ) && self::is_module_active( 'back_in_stock_notifier' );
2528 + }
2252 2529
2530 + /**
2531 + * Elementor Widget Active.
2532 + *
2533 + * @param string $widget Elementor Widget.
2534 + *
2535 + * @return true|void
2536 + */
2537 + public static function is_elementor_widget_active( $widget ) {
2538 + $element_list = self::get_widgets_list();
2539 +
2540 + if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2541 + return true;
2542 + }
2543 + }
2544 +
2253 2545 /***
2254 2546 * Save Settings data
2255 2547 *
2256 - * @param $section_id
2257 - * @param $block_id
2258 - * @param $rawOptions
2548 + * @param string $section_id Section ID.
2549 + * @param string $block_id Block ID.
2550 + * @param array $rawOptions Raw Options.
2259 2551 *
2260 2552 * @return array
2261 2553 */
2262 - public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2554 + public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2263 2555 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2264 2556 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2265 2557 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2266 2558 $results = [
@@ -2286,9 +2578,9 @@
2286 2578 $fields = [];
2287 2579 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2288 2580 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2289 2581 }
2290 -
2582 + do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2291 2583 if ( empty( $fields ) ) {
2292 2584 if ( isset( $rawOptions['active'] ) ) {
2293 2585 $changed = true;
2294 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2294,15 +2586,15 @@
2294 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2295 2587 }
2296 2588 } else {
2297 2589 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2298 - if ( $raw_option_key === 'active' ) {
2590 + if ( 'active' === $raw_option_key ) {
2299 2591 $changed = true;
2300 - $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : '';
2592 + $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2301 2593 } else {
2302 2594 if ( isset( $fields[ $raw_option_key ] ) ) {
2303 2595 $field = $fields[ $raw_option_key ];
2304 - if ( $field['type'] === 'switch' ) {
2596 + if ( 'switch' === $field['type'] ) {
2305 2597 $value = 'on' === $raw_value ? 'on' : '';
2306 2598 } elseif ( 'repeaters' === $field['type'] ) {
2307 2599 $the_value = [];
2308 2600 $rep_title = [];
@@ -2307,10 +2599,14 @@
2307 2599 $the_value = [];
2308 2600 $rep_title = [];
2309 2601 if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2310 2602 foreach ( $raw_value as $key => $value ) {
2311 - $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2312 - $cr = $the_value_decoded->title ?? '';
2603 + if ( is_string( $value ) ) {
2604 + $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2605 + } else {
2606 + $the_value_decoded = $value;
2607 + }
2608 + $cr = $the_value_decoded->title ?? '';
2313 2609 if ( in_array( $cr, $rep_title, true ) ) {
2314 2610 continue;
2315 2611 }
2316 2612 $rep_title[] = $cr;
@@ -2318,11 +2614,21 @@
2318 2614 }
2319 2615 } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2320 2616 $the_value = $raw_value;
2321 2617 }
2322 - $value = wp_json_encode( $the_value );
2618 + $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2619 + } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2620 + $manual_field_value = [];
2621 + if ( is_array( $raw_value ) ) {
2622 + foreach ( $raw_value as $key => $value ) {
2623 + $manual_field_value[] = json_decode( stripslashes( $value ), true );
2624 + }
2625 + $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2626 + } elseif ( is_string( $raw_value ) ) {
2627 + $value = $raw_value;
2628 + }
2323 2629 } else {
2324 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2630 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2325 2631 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2326 2632 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2327 2633 $value = array_map( $field['sanitize_fn'], $raw_value );
2328 2634 } else {
@@ -2344,9 +2650,9 @@
2344 2650 $value = sanitize_text_field( $raw_value );
2345 2651 }
2346 2652 }
2347 2653 }
2348 - $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null;
2654 + $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 2655 $changed = true;
2350 2656 }
2351 2657 }
2352 2658 }
@@ -2366,27 +2672,15 @@
2366 2672
2367 2673 return $results;
2368 2674 }
2369 2675
2370 - /***
2371 - * @param $meta_key
2372 - * @param $meta_value
2676 + /**
2677 + * Count products by taxonomies.
2373 2678 *
2374 - * @return mixed|void
2679 + * @param array $terms Terms.
2680 + * @param string $taxonomy Taxonomy.
2375 2681 *
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 - * }
2682 + * @return int|void
2389 2683 */
2390 2684 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2391 2685 if ( empty( $terms ) || ! is_array( $terms ) ) {
2392 2686 return;
@@ -2398,17 +2692,73 @@
2398 2692 ];
2399 2693
2400 2694 if ( 'product_cat' === $taxonomy ) {
2401 2695 $args['product_category_id'] = $terms;
2696 + } elseif ( 'product_brand' === $taxonomy ) {
2697 + $args['product_brand_id'] = $terms;
2402 2698 } else {
2403 2699 $args['product_tag_id'] = $terms;
2404 2700 }
2405 2701
2406 2702 $query = new WC_Product_Query( $args );
2703 +
2407 2704 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2408 2705 }
2409 2706
2410 2707 /**
2708 + * Count products by attribute terms.
2709 + *
2710 + * @param array $term_ids Term IDs.
2711 + * @param string $relation Relation.
2712 + *
2713 + * @return int
2714 + */
2715 + public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2716 + if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2717 + return 0;
2718 + }
2719 +
2720 + $terms_by_taxonomy = [];
2721 +
2722 + foreach ( $term_ids as $term_id ) {
2723 + $term = get_term( $term_id );
2724 + if ( ! is_wp_error( $term ) && $term ) {
2725 + if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2726 + $terms_by_taxonomy[ $term->taxonomy ] = [];
2727 + }
2728 + $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2729 + }
2730 + }
2731 +
2732 + if ( empty( $terms_by_taxonomy ) ) {
2733 + return 0;
2734 + }
2735 +
2736 + $args = [
2737 + 'status' => 'publish',
2738 + 'limit' => -1,
2739 + 'return' => 'ids',
2740 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2741 + 'relation' => strtoupper( $relation ),
2742 + ],
2743 + ];
2744 +
2745 + foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2746 + $args['tax_query'][] = [
2747 + 'taxonomy' => $taxonomy,
2748 + 'field' => 'term_id',
2749 + 'terms' => $terms,
2750 + 'operator' => 'IN',
2751 + ];
2752 + }
2753 +
2754 + $query = new WC_Product_Query( $args );
2755 + $products = $query->get_products();
2756 +
2757 + return count( $products );
2758 + }
2759 +
2760 + /**
2411 2761 * Check if product filters widget has ajax.
2412 2762 *
2413 2763 * @param string $page Page name.
2414 2764 *
@@ -2479,9 +2829,9 @@
2479 2829 if ( ! is_admin() ) {
2480 2830 return [];
2481 2831 }
2482 2832
2483 - $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) );
2833 + $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2484 2834
2485 2835 if ( isset( self::$cache[ $cache_key ] ) ) {
2486 2836 return self::$cache[ $cache_key ];
2487 2837 }
@@ -2523,9 +2873,65 @@
2523 2873 // Cache the results in a static variable for the next call.
2524 2874 self::$cache[ $cache_key ] = $cats;
2525 2875 return $cats;
2526 2876 }
2877 + /**
2878 + * Get WooCommerce product categories.
2879 + *
2880 + * @param string|null $search_query The search string for category names.
2881 + *
2882 + * @return array An array of product categories with 'value' and 'label' keys.
2883 + */
2884 + public static function products_tags_query( $search_query = null ) {
2885 + if ( ! is_admin() ) {
2886 + return [];
2887 + }
2527 2888
2889 + $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2890 +
2891 + if ( isset( self::$cache[ $cache_key ] ) ) {
2892 + return self::$cache[ $cache_key ];
2893 + }
2894 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2895 + if ( ! $results ) {
2896 + global $wpdb;
2897 + $sql = "SELECT t.term_id, t.name
2898 + FROM {$wpdb->terms} t
2899 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2900 + WHERE tt.taxonomy = 'product_tag'";
2901 +
2902 + if ( $search_query ) {
2903 + $sql .= ' AND t.name LIKE %s';
2904 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2905 + } else {
2906 + $prepared_sql = $sql; // No need for placeholders.
2907 + }
2908 +
2909 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2910 + // Cache the results in the object cache for future use.
2911 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2912 + Cache::set_data_cache_key( $cache_key );
2913 + }
2914 +
2915 + $cats = [];
2916 + if ( ! is_array( $results ) && ! count( $results ) ) {
2917 + return $cats;
2918 + }
2919 + foreach ( $results as $row ) {
2920 + $category_id = $row->term_id;
2921 + $category_title = $row->name;
2922 +
2923 + $cats[] = [
2924 + 'value' => $category_id,
2925 + 'label' => $category_title,
2926 + ];
2927 + }
2928 +
2929 + // Cache the results in a static variable for the next call.
2930 + self::$cache[ $cache_key ] = $cats;
2931 + return $cats;
2932 + }
2933 +
2528 2934 /**
2529 2935 * @param string $search_query Search query.
2530 2936 *
2531 2937 * @return array
@@ -2583,8 +2989,9 @@
2583 2989 /**
2584 2990 * Get Post Types.
2585 2991 *
2586 2992 * @param string $search_query Search query.
2993 + * @param array $args Arguments.
2587 2994 *
2588 2995 * @return array
2589 2996 */
2590 2997 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2595,9 +3002,8 @@
2595 3002 'post_type' => 'any',
2596 3003 'posts_per_page' => 20,
2597 3004 'orderby' => 'ID',
2598 3005 'order' => 'DESC',
2599 - // 'suppress_filters' => false, // WPML Support, Remove Others Languages.
2600 3006 ]
2601 3007 );
2602 3008
2603 3009 if ( 'archive' !== $args['post_type'] ) {
@@ -2608,11 +3014,12 @@
2608 3014 $query_results = get_posts( $args );
2609 3015 foreach ( $query_results as $post ) {
2610 3016 $posts[] = [
2611 3017 'value' => $post->ID,
2612 - 'label' => $post->post_title . ' ( ID ' . $post->ID . ')',
3018 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2613 3019 ];
2614 3020 }
3021 +
2615 3022 if ( $search_query ) {
2616 3023 if ( strpos( '-error 404', $search_query ) ) {
2617 3024 $posts[] = [
2618 3025 'value' => 'error',
@@ -2619,12 +3026,14 @@
2619 3026 'label' => __( '404 Error', 'shopbuilder' ),
2620 3027 ];
2621 3028 }
2622 3029 } else {
2623 - $posts[] = [
2624 - 'value' => 'error',
2625 - 'label' => __( '404 Error', 'shopbuilder' ),
2626 - ];
3030 + if ( 'page' === $args['post_type'] ) {
3031 + $posts[] = [
3032 + 'value' => 'error',
3033 + 'label' => __( '404 Error', 'shopbuilder' ),
3034 + ];
3035 + }
2627 3036 }
2628 3037 } else {
2629 3038 $posts = self::get_registered_post_types( $search_query );
2630 3039 }
@@ -2652,9 +3061,9 @@
2652 3061 public static function renderView( $viewName, $args = [], $return = false ) {
2653 3062 $viewName = str_replace( '.', '/', $viewName );
2654 3063
2655 3064 if ( ! empty( $args ) && is_array( $args ) ) {
2656 - extract( $args );
3065 + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
2657 3066 }
2658 3067
2659 3068 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2660 3069
@@ -2676,9 +3085,9 @@
2676 3085
2677 3086 /**
2678 3087 * Best selling product query.
2679 3088 *
2680 - * @param int $minimum_sale Minimum sale/
3089 + * @param int $minimum_sale Minimum sale.
2681 3090 * @param int $limit Total limit.
2682 3091 *
2683 3092 * @return array|mixed
2684 3093 */
@@ -2736,12 +3145,14 @@
2736 3145
2737 3146 if ( isset( self::$cache[ $cache_key ] ) ) {
2738 3147 return self::$cache[ $cache_key ];
2739 3148 }
3149 + $date_created = $product->get_date_created();
3150 + if ( ! $date_created ) {
3151 + return false;
3152 + }
3153 + $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
2740 3154
2741 - // Get the product publish date.
2742 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2743 -
2744 3155 // Calculate the difference in days.
2745 3156 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2746 3157
2747 3158 // Check if the product is considered new.
@@ -2769,12 +3180,9 @@
2769 3180
2770 3181 /**
2771 3182 * Checks if a feature is disabled for guest users based on the provided option.
2772 3183 *
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
3184 + * @return void
2777 3185 */
2778 3186 public static function woocommerce_output_all_notices() {
2779 3187 if ( function_exists( 'wc_print_notices' ) ) :
2780 3188 echo '<div class="rtsb-notice">';
@@ -2876,8 +3284,68 @@
2876 3284 ];
2877 3285 }
2878 3286
2879 3287 /**
3288 + * Retrieve an array of custom icons with their HTML representation.
3289 + *
3290 + * @return array
3291 + */
3292 + public static function get_icons() {
3293 + $icon = self::get_custom_icon_names();
3294 + $icons_array = [];
3295 + foreach ( $icon as $value ) {
3296 + $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3297 + }
3298 + return $icons_array;
3299 + }
3300 +
3301 + /**
3302 + * Generates HTML markup for displaying an endpoint icon.
3303 + *
3304 + * @param array $data The endpoint data containing icon information.
3305 + * @param boolean $wrapper Whether to wrap the icon in a span element.
3306 + *
3307 + * @return string
3308 + */
3309 + public static function get_icon_html( $data, $wrapper = true ) {
3310 + if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3311 + return '';
3312 + }
3313 + $endpoint = '';
3314 + $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3315 +
3316 + if ( 'select_icon' === $data['icon_source'] ) {
3317 + $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3318 + } else {
3319 + $icon_html = '';
3320 + $icon_url = $data['image_icon']['source'] ?? '';
3321 + $icon_id = $data['image_icon']['id'] ?? 0;
3322 + $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3323 +
3324 + if ( 'svg' === $extension ) {
3325 + $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3326 + $is_svg = strpos( $content, '<svg' );
3327 +
3328 + if ( false !== $is_svg ) {
3329 + $icon_html = substr( $content, $is_svg );
3330 + }
3331 + } else {
3332 + $attr = [
3333 + 'class' => 'rtsb-icon-img',
3334 + 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3335 + ];
3336 +
3337 + $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3338 + }
3339 +
3340 + $html .= $icon_html;
3341 + }
3342 +
3343 + $html .= $wrapper ? '</span>' : '';
3344 +
3345 + return $html;
3346 + }
3347 + /**
2880 3348 * Flushes rewrite rules.
2881 3349 *
2882 3350 * @return void
2883 3351 */
@@ -2917,12 +3385,18 @@
2917 3385
2918 3386 /**
2919 3387 * Social Share Platforms.
2920 3388 *
2921 - * @return mixed|null
3389 + * @param string $section_id Section ID.
3390 + * @param string $block_id Block ID.
3391 + * @param string $option_key Option key.
3392 + * @param string $compare_key Compare key.
3393 + * @param string $compare_value Compare value.
3394 + * @param array $values Values.
3395 + *
3396 + * @return void
2922 3397 */
2923 3398 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 3399 $modules = DataModel::source()->get_option( $section_id, [], false );
2926 3400 if ( empty( $modules[ $block_id ] ) ) {
2927 3401 return;
2928 3402 }
@@ -2960,6 +3434,675 @@
2960 3434 return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
2961 3435 }
2962 3436 );
2963 3437 return $ids;
3438 + }
3439 +
3440 + /**
3441 + * Generate and cache dynamic CSS styles.
3442 + *
3443 + * @param array $options CSS options to apply (e.g. padding, margin).
3444 + * @param string $cache_key Cache key for the CSS.
3445 + * @param array $css_properties An array of CSS properties with selectors and properties.
3446 + *
3447 + * @return void
3448 + */
3449 + public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3450 + $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3451 + $style_handle = self::optimized_handle( 'rtsb-frontend' );
3452 +
3453 + if ( false !== $cached_css ) {
3454 + wp_add_inline_style( $style_handle, $cached_css );
3455 + return;
3456 + }
3457 +
3458 + $css_rules = [];
3459 + $grouped_css_rules = [];
3460 +
3461 + foreach ( $css_properties as $option => $details ) {
3462 + if ( ! empty( $options[ $option ] ) ) {
3463 + $selector = $details['selector'] ?? '';
3464 + $property = $details['property'] ?? '';
3465 + $unit = $details['unit'] ?? '';
3466 + $value = $options[ $option ];
3467 +
3468 + if ( empty( $grouped_css_rules[ $selector ] ) ) {
3469 + $grouped_css_rules[ $selector ] = [];
3470 + }
3471 +
3472 + if ( is_array( $property ) ) {
3473 + foreach ( $property as $prop ) {
3474 + $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3475 + }
3476 + } else {
3477 + $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3478 + }
3479 + }
3480 + }
3481 +
3482 + foreach ( $grouped_css_rules as $selector => $properties ) {
3483 + $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3484 + }
3485 +
3486 + $dynamic_css = implode( ' ', $css_rules );
3487 +
3488 + wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3489 + Cache::set_data_cache_key( $cache_key );
3490 +
3491 + if ( ! empty( $dynamic_css ) ) {
3492 + wp_add_inline_style( $style_handle, $dynamic_css );
3493 + }
3494 + }
3495 +
3496 + /**
3497 + * Pro version notice.
3498 + *
3499 + * @param string $ver Pro Version.
3500 + * @param string $tab Tab.
3501 + * @param string $name Name.
3502 + * @param bool $enable_free_Link Enable free link.
3503 + *
3504 + * @return array[]
3505 + */
3506 + public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3507 + return [
3508 + 'version_check' => [
3509 + 'id' => 'version_check',
3510 + 'type' => 'title',
3511 + 'label' => sprintf(
3512 + /* translators: 1: required version, 2: link to the Plugins page */
3513 + esc_html__(
3514 + 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3515 + 'shopbuilder'
3516 + ),
3517 + $name,
3518 + sprintf(
3519 + '<br /><u><b>%s</b></u>',
3520 + esc_html( $ver ?? '1.5.0' ) . ' or higher'
3521 + ),
3522 + $enable_free_Link
3523 + ? sprintf(
3524 + '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3525 + esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3526 + esc_url( admin_url( 'plugins.php' ) ),
3527 + esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3528 + esc_html__( 'from the Plugins page', 'shopbuilder' )
3529 + )
3530 + : ''
3531 + ),
3532 + 'tab' => $tab,
3533 + 'customClass' => 'checkout-notice',
3534 + ],
3535 + ];
3536 + }
3537 +
3538 + /**
3539 + * Get product gallery ids.
3540 + *
3541 + * @param object $product Product object.
3542 + *
3543 + * @return mixed
3544 + */
3545 + public static function get_cached_gallery_ids( $product ) {
3546 + $product_id = $product->get_id();
3547 + $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3548 +
3549 + if ( isset( self::$cache[ $cache_key ] ) ) {
3550 + return self::$cache[ $cache_key ];
3551 + }
3552 +
3553 + $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3554 +
3555 + if ( false !== $cached_result ) {
3556 + self::$cache[ $cache_key ] = $cached_result;
3557 +
3558 + return $cached_result;
3559 + }
3560 +
3561 + $gallery_ids = $product->get_gallery_image_ids();
3562 + self::$cache[ $cache_key ] = $gallery_ids;
3563 +
3564 + wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3565 + Cache::set_data_cache_key( $cache_key );
3566 +
3567 + return $gallery_ids;
3568 + }
3569 +
3570 + /**
3571 + * Check if optimization settings are enabled.
3572 + *
3573 + * @return bool
3574 + */
3575 + public static function is_optimization_enabled() {
3576 + $has_pro = rtsb()->has_pro();
3577 + $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3578 +
3579 + if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3580 + return false;
3581 + }
3582 +
3583 + $generalList = GeneralList::instance()->get_data();
3584 +
3585 + return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3586 + }
3587 +
3588 + /**
3589 + * Get the optimized handle.
3590 + *
3591 + * @param string $handle Base handle used for script/style IDs.
3592 + *
3593 + * @return string
3594 + */
3595 + public static function optimized_handle( $handle ) {
3596 + $use_optimization = self::is_optimization_enabled();
3597 +
3598 + if ( $use_optimization ) {
3599 + $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3600 + }
3601 +
3602 + return $handle;
3603 + }
3604 +
3605 + /**
3606 + * Get the optimized handle by context.
3607 + *
3608 + * @return string
3609 + */
3610 + public static function get_optimized_handle_by_context() {
3611 + $context = self::detect_context();
3612 +
3613 + if ( 'account' === $context ) {
3614 + return 'rtsb-bundled-global';
3615 + }
3616 +
3617 + return "rtsb-bundled-$context";
3618 + }
3619 +
3620 + /**
3621 + * Check if Elementor page.
3622 + *
3623 + * @param int $post_id Post ID.
3624 + *
3625 + * @return bool
3626 + */
3627 + public static function is_elementor_page( $post_id = null ) {
3628 + if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3629 + return false;
3630 + }
3631 +
3632 + $post_id = $post_id ?: get_the_ID();
3633 +
3634 + if ( ! $post_id ) {
3635 + return true;
3636 + }
3637 +
3638 + return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3639 + }
3640 +
3641 + /**
3642 + * Check if shop or archive.
3643 + *
3644 + * @return bool
3645 + */
3646 + public static function is_shop_or_archive() {
3647 + return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3648 + }
3649 +
3650 + /**
3651 + * Detect context.
3652 + *
3653 + * @return string
3654 + */
3655 + public static function detect_context() {
3656 + switch ( true ) {
3657 + case is_product() || BuilderFns::is_product():
3658 + $context = 'product';
3659 + break;
3660 +
3661 + case is_cart() || BuilderFns::is_cart():
3662 + $context = 'cart';
3663 + break;
3664 +
3665 + case is_checkout() || BuilderFns::is_checkout():
3666 + $context = 'checkout';
3667 + break;
3668 +
3669 + case self::is_shop_or_archive():
3670 + $context = 'shop';
3671 + break;
3672 +
3673 + default:
3674 + $context = 'global';
3675 + break;
3676 + }
3677 +
3678 + return apply_filters( 'rtsb/optimizer/context_detect', $context );
3679 + }
3680 +
3681 + /**
3682 + * Enqueue optimized assets.
3683 + *
3684 + * @return void
3685 + */
3686 + public static function enqueue_optimized_assets() {
3687 + $asset_registry = AssetRegistry::instance();
3688 + $bundled_assets = $asset_registry->get_bundled_assets();
3689 + $context = self::detect_context();
3690 + $contextual_loading = self::is_contextual_loading();
3691 + $theme_handle = self::find_theme_stylesheet_handle();
3692 +
3693 + if ( $contextual_loading ) {
3694 + // Enqueue JS.
3695 + if ( ! empty( $bundled_assets['js'] ) ) {
3696 + $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3697 +
3698 + if ( $js_context ) {
3699 + wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3700 + }
3701 + }
3702 +
3703 + // Enqueue CSS.
3704 + if ( ! empty( $bundled_assets['css'] ) ) {
3705 + $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3706 +
3707 + if ( $css_context ) {
3708 + $handle = $bundled_assets['css'][ $css_context ]['handle'];
3709 +
3710 + wp_enqueue_style( $handle );
3711 +
3712 + if ( ! empty( $theme_handle ) ) {
3713 + self::set_theme_dependency( $theme_handle, $handle );
3714 + }
3715 + }
3716 + }
3717 + } else {
3718 + // Enqueue JS.
3719 + if ( ! empty( $bundled_assets['js'] ) ) {
3720 + wp_enqueue_script( $bundled_assets['js']['handle'] );
3721 + }
3722 +
3723 + // Enqueue CSS.
3724 + if ( ! empty( $bundled_assets['css'] ) ) {
3725 + $handle = $bundled_assets['css']['handle'];
3726 +
3727 + wp_enqueue_style( $handle );
3728 +
3729 + if ( ! empty( $theme_handle ) ) {
3730 + self::set_theme_dependency( $theme_handle, $handle );
3731 + }
3732 + }
3733 + }
3734 + }
3735 +
3736 + /**
3737 + * Find theme stylesheet handle.
3738 + *
3739 + * @return string|false
3740 + */
3741 + private static function find_theme_stylesheet_handle() {
3742 + static $cached_handle = null;
3743 +
3744 + if ( null !== $cached_handle ) {
3745 + return $cached_handle;
3746 + }
3747 +
3748 + global $wp_styles;
3749 +
3750 + if ( ! $wp_styles instanceof WP_Styles ) {
3751 + return false;
3752 + }
3753 +
3754 + $stylesheet_uri = get_stylesheet_uri();
3755 + $theme_directory_uri = get_stylesheet_directory_uri();
3756 +
3757 + $hash = md5( $stylesheet_uri );
3758 + $transient_key = 'rtsb_theme_css_handle_' . $hash;
3759 +
3760 + $transient = get_transient( $transient_key );
3761 +
3762 + if ( false !== $transient ) {
3763 + $cached_handle = $transient;
3764 +
3765 + return $transient;
3766 + }
3767 +
3768 + foreach ( $wp_styles->registered as $handle => $style ) {
3769 + $src = $style->src;
3770 +
3771 + if (
3772 + ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3773 + strpos( $src, 'style.css' ) !== false
3774 + ) {
3775 + set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3776 + Cache::set_transient_cache_key( $transient_key );
3777 + $cached_handle = $handle;
3778 +
3779 + return $handle;
3780 + }
3781 + }
3782 +
3783 + $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3784 +
3785 + if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3786 + set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3787 + Cache::set_transient_cache_key( $transient_key );
3788 + $cached_handle = $filtered_handle;
3789 +
3790 + return $filtered_handle;
3791 + }
3792 +
3793 + set_transient( $transient_key, false, DAY_IN_SECONDS );
3794 + Cache::set_transient_cache_key( $transient_key );
3795 + $cached_handle = false;
3796 +
3797 + return false;
3798 + }
3799 +
3800 + /**
3801 + * Set theme dependency.
3802 + *
3803 + * @param string $theme_handle Theme handle.
3804 + * @param string $our_handle Our handle.
3805 + *
3806 + * @return void
3807 + */
3808 + private static function set_theme_dependency( $theme_handle, $our_handle ) {
3809 + global $wp_styles;
3810 +
3811 + if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3812 + return;
3813 + }
3814 +
3815 + $theme_style = $wp_styles->registered[ $theme_handle ];
3816 +
3817 + // Add our handle as a dependency if it's not already there.
3818 + if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3819 + $theme_style->deps[] = $our_handle;
3820 + }
3821 + }
3822 +
3823 + /**
3824 + * Check if Elementor scripts should be loaded.
3825 + *
3826 + * @return bool
3827 + */
3828 + public static function should_load_elementor_scripts() {
3829 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3830 +
3831 + $enable_optimization = $data['enable_optimization'] ?? 'on';
3832 + $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3833 +
3834 + if ( 'on' !== $enable_optimization ) {
3835 + return true;
3836 + }
3837 +
3838 + return 'on' === $load_elementor_scripts;
3839 + }
3840 +
3841 + /**
3842 + * Locate asset.
3843 + *
3844 + * @param string $relative_path Relative path.
3845 + *
3846 + * @return string|null
3847 + */
3848 + public static function locate_asset( $relative_path ) {
3849 + $free_context = rtsb();
3850 + $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3851 +
3852 + $paths = [];
3853 +
3854 + if ( $pro_context ) {
3855 + $paths[] = $pro_context->get_assets_path( $relative_path );
3856 + }
3857 +
3858 + $paths[] = $free_context->get_assets_path( $relative_path );
3859 +
3860 + foreach ( $paths as $path ) {
3861 + if ( file_exists( $path ) ) {
3862 + return $path;
3863 + }
3864 + }
3865 +
3866 + return null;
3867 + }
3868 +
3869 + /**
3870 + * Enqueue module assets.
3871 + *
3872 + * @param string $handle Asset handle.
3873 + * @param string $module_name Module name.
3874 + * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3875 + *
3876 + * @return string
3877 + */
3878 + public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3879 + $use_optimization = self::is_optimization_enabled();
3880 + $handle = self::optimized_handle( $handle );
3881 +
3882 + if ( $use_optimization ) {
3883 + return $handle;
3884 + }
3885 +
3886 + $defaults = [
3887 + 'type' => 'both',
3888 + 'deps' => [ 'jquery', 'rtsb-public' ],
3889 + 'context' => null,
3890 + 'version' => RTSB_VERSION,
3891 + ];
3892 +
3893 + $config = array_merge( $defaults, $options );
3894 + $rtl_suffix = is_rtl() ? '-rtl' : '';
3895 + $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3896 + $context = $config['context'] ?: rtsb();
3897 + $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3898 + $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3899 +
3900 + // Register CSS if enabled.
3901 + if ( $load_css ) {
3902 + wp_register_style(
3903 + $handle,
3904 + $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3905 + [],
3906 + $config['version']
3907 + );
3908 + }
3909 +
3910 + // Register JS if enabled.
3911 + if ( $load_js ) {
3912 + wp_register_script(
3913 + $handle,
3914 + $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3915 + $config['deps'],
3916 + $config['version'],
3917 + true
3918 + );
3919 + }
3920 +
3921 + if ( $load_css ) {
3922 + wp_enqueue_style( $handle );
3923 +
3924 + $theme_handle = self::find_theme_stylesheet_handle();
3925 +
3926 + if ( ! empty( $theme_handle ) ) {
3927 + self::set_theme_dependency( $theme_handle, $handle );
3928 + }
3929 + }
3930 +
3931 + if ( $load_js ) {
3932 + wp_enqueue_script( $handle );
3933 + }
3934 +
3935 + return $handle;
3936 + }
3937 +
3938 + /**
3939 + * Check if contextual loading is enabled.
3940 + *
3941 + * @return bool
3942 + */
3943 + public static function is_contextual_loading() {
3944 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3945 +
3946 + return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3947 + }
3948 +
3949 + /**
3950 + * Get Modules list with cache support.
3951 + *
3952 + * @return array
3953 + */
3954 + public static function get_modules_list() {
3955 + static $cached_modules = null;
3956 +
3957 + if ( null !== $cached_modules ) {
3958 + return $cached_modules;
3959 + }
3960 +
3961 + $cache_key = 'rtsb_module_list';
3962 + $cache_group = 'shopbuilder';
3963 +
3964 + $cached = wp_cache_get( $cache_key, $cache_group );
3965 +
3966 + if ( false !== $cached ) {
3967 + $cached_modules = $cached;
3968 +
3969 + return $cached;
3970 + }
3971 +
3972 + $modules = ModuleList::instance()->get_data();
3973 +
3974 + wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3975 + Cache::set_data_cache_key( $cache_key );
3976 +
3977 + $cached_modules = $modules;
3978 +
3979 + return $modules;
3980 + }
3981 +
3982 + /**
3983 + * Get Elementor widgets list with cache support.
3984 + *
3985 + * @return array
3986 + */
3987 + public static function get_widgets_list() {
3988 + static $cached_widgets = null;
3989 +
3990 + if ( null !== $cached_widgets ) {
3991 + return $cached_widgets;
3992 + }
3993 +
3994 + $cache_key = 'rtsb_elementor_widget_list';
3995 + $cache_group = 'shopbuilder';
3996 +
3997 + $cached = wp_cache_get( $cache_key, $cache_group );
3998 +
3999 + if ( false !== $cached ) {
4000 + $cached_widgets = $cached;
4001 +
4002 + return $cached;
4003 + }
4004 +
4005 + $widgets = ElementList::instance()->get_list();
4006 +
4007 + wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
4008 + Cache::set_data_cache_key( $cache_key );
4009 +
4010 + $cached_widgets = $widgets;
4011 +
4012 + return $widgets;
4013 + }
4014 +
4015 + /**
4016 + * Convert the given price to the active currency.
4017 + *
4018 + * @param float $price The original price.
4019 + * @param Object||null $product Product.
4020 + *
4021 + * @return float
4022 + */
4023 + public static function get_currency_base_price( $price, $product = null ) {
4024 + return apply_filters( 'rtsb/convert/currency/price', $price, $product );
4025 + }
4026 + /**
4027 + * Generate a signed URL with a payload.
4028 + *
4029 + * @param array $payload Associative data to encode.
4030 + * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
4031 + * @param string $key Key name to use in the URL.
4032 + * @return string Signed URL with key parameter.
4033 + */
4034 + public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
4035 + if ( empty( $key ) ) {
4036 + $key = 'key';
4037 + }
4038 + // Convert payload to JSON.
4039 + $data = wp_json_encode( $payload );
4040 + // Create signature.
4041 + $signature = wp_hash( $data );
4042 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
4043 + $encode_key = base64_encode( $data . '::' . $signature );
4044 + // Return full URL with key param.
4045 + return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
4046 + }
4047 +
4048 + /**
4049 + * Decode and verify a signed URL key.
4050 + *
4051 + * @param string $key Encoded key from URL.
4052 + * @return array|false Decoded payload array on success, false on failure.
4053 + */
4054 + public static function decode_signed_key( string $key ) {
4055 + if ( empty( $key ) ) {
4056 + return false;
4057 + }
4058 + $key = rawurldecode( wp_unslash( $key ) );
4059 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
4060 + $raw = base64_decode( sanitize_text_field( $key ) );
4061 + if ( ! $raw ) {
4062 + return false;
4063 + }
4064 + $parts = explode( '::', $raw, 2 );
4065 + if ( count( $parts ) !== 2 ) {
4066 + return false;
4067 + }
4068 + list( $data_json, $signature ) = $parts;
4069 + // Validate signature.
4070 + if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4071 + return false;
4072 + }
4073 + $payload = json_decode( $data_json, true );
4074 + return is_array( $payload ) ? $payload : false;
4075 + }
4076 +
4077 + /**
4078 + * Get Loco Translate MO file path for a plugin.
4079 + *
4080 + * @param string $textdomain Plugin textdomain.
4081 + * @return void|false
4082 + */
4083 + public static function load_loco_textdomain( $textdomain ) {
4084 + if ( ! function_exists( 'loco_plugin_version' ) ) {
4085 + return false;
4086 + }
4087 + $lang = WP_LANG_DIR;
4088 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4089 + if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4090 + $lang = LOCO_LANG_DIR;
4091 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4092 + }
4093 + if ( ! file_exists( $path ) ) {
4094 + if ( 'shopbuilder' === $textdomain ) {
4095 + $plugin_root = dirname( RTSB_FILE );
4096 + } elseif ( 'shopbuilder-pro' === $textdomain ) {
4097 + $plugin_root = dirname( RTSBPRO_FILE );
4098 + } else {
4099 + return false;
4100 + }
4101 + $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4102 + }
4103 + if ( ! file_exists( $path ) ) {
4104 + return false;
4105 + }
4106 + load_textdomain( $textdomain, $path );
2964 4107 }
2965 4108 }