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 +1325 -153 2.1.123.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,34 +97,60 @@
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 - if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
62 - session_start();
141 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
142 + session_start();
63 143 $_SESSION[ $name ] = $value;
64 - //session_write_close();
65 144 } else {
66 145 $_SESSION[ $name ] = $value;
67 146 }
147 + }
68 148
69 - }
70 149 /**
71 150 * Get Cookie or Session
72 151 *
73 - * @param $name
152 + * @param string $name Name.
74 153 *
75 154 * @return bool
76 155 */
77 156 public static function getSession( $name ) {
@@ -77,9 +156,9 @@
77 156 public static function getSession( $name ) {
78 157 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
79 158 session_start();
80 159 }
81 - return $_SESSION[ $name ] ?? null;
160 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
82 161 }
83 162 /**
84 163 * Remove Session Variable
85 164 *
@@ -88,17 +167,47 @@
88 167 public static function removeSession( $name ) {
89 168 if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
90 169 session_start();
91 170 unset( $_SESSION[ $name ] );
92 - //session_write_close();
93 171 } else {
94 172 unset( $_SESSION[ $name ] );
95 173 }
96 174 }
97 -
98 175 /**
99 - * @param $plugin_slug
176 + * Gets the current timestamp in WordPress timezone.
100 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 + *
101 210 * @return bool
102 211 */
103 212 public static function check_plugin_installed( $plugin_slug ): bool {
104 213 $installed_plugins = get_plugins();
@@ -106,10 +215,12 @@
106 215 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
107 216 }
108 217
109 218 /**
110 - * @param $plugin_slug
219 + * Check if a plugin is active.
111 220 *
221 + * @param string $plugin_slug Plugin slug.
222 + *
112 223 * @return bool
113 224 */
114 225 public static function check_plugin_active( $plugin_slug ): bool {
115 226 if ( is_plugin_active( $plugin_slug ) ) {
@@ -117,21 +228,71 @@
117 228 }
118 229
119 230 return false;
120 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' );
121 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 +
122 270 /**
123 - * @param $data
271 + * Stripslashes value.
124 272 *
273 + * @param mixed $data Data.
274 + *
125 275 * @return mixed|string
126 276 */
127 277 public static function stripslashes_value( $data ) {
128 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
129 - return stripslashes( $data );
130 - } elseif ( is_array( $data ) ) {
278 + if ( is_array( $data ) ) {
131 279 foreach ( $data as $key => $value ) {
132 280 $data[ $key ] = self::stripslashes_value( $value );
133 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 + }
134 295 }
135 296
136 297 return $data;
137 298 }
@@ -136,10 +297,12 @@
136 297 return $data;
137 298 }
138 299
139 300 /**
140 - * @param $string
301 + * Multiselect settings field value
141 302 *
303 + * @param string $string String.
304 + *
142 305 * @return array
143 306 */
144 307 public static function multiselect_settings_field_value( $string ) {
145 308
@@ -159,8 +322,10 @@
159 322 return $values;
160 323 }
161 324
162 325 /**
326 + * Check if is block theme
327 + *
163 328 * @return bool
164 329 */
165 330 public static function check_is_block_theme(): bool {
166 331 global $wp_version;
@@ -168,16 +333,19 @@
168 333 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
169 334 }
170 335
171 336 /**
337 + * Locate template
338 + *
172 339 * @param string $template_name Template name.
173 340 * @param string $template_path Template path. (default: '').
174 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
341 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
175 342 *
176 343 * @return mixed|void
177 344 */
178 345 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
179 - $template_name = $template_name . '.php';
346 + $template_name = self::sanitize_file_name( $template_name ) . '.php';
347 +
180 348 if ( ! $template_path ) {
181 349 $template_path = rtsb()->get_template_path();
182 350 }
183 351 if ( ! $plugin_path ) {
@@ -199,12 +367,12 @@
199 367 )
200 368 );
201 369
202 370 if ( ! $located ) {
203 - 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 ) ) {
204 374 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
205 - } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
206 - return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
207 375 }
208 376 }
209 377
210 378 /**
@@ -220,8 +388,19 @@
220 388 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
221 389 }
222 390
223 391 /**
392 + * Remove any character that is not alphanumeric, /, _, or -.
393 + *
394 + * @param string $name Name to sanitize.
395 + *
396 + * @return array|string|string[]|null
397 + */
398 + private static function sanitize_file_name( $name ) {
399 + return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
400 + }
401 +
402 + /**
224 403 * Template Content
225 404 *
226 405 * @param string $template_name Template name.
227 406 * @param array $args Arguments. (default: array).
@@ -230,11 +409,20 @@
230 409 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
231 410 *
232 411 * @return false|string|void
233 412 */
234 - public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
235 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
236 -
413 + public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
414 + $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
415 + $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
416 + if ( ! $located ) {
417 + $located = self::locate_template( $template_name, $template_path, $plugin_path );
418 + // Don't cache the absolute path so that it can be shared between web servers with different paths.
419 + $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
420 + Cache::set_template_cache( $cache_key, $cache_path );
421 + } else {
422 + // Make sure that the absolute path to the template is resolved.
423 + $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
424 + }
237 425 if ( ! file_exists( $located ) ) {
238 426 // translators: %s template.
239 427 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
240 428
@@ -242,9 +430,9 @@
242 430 }
243 431
244 432 if ( ! empty( $args ) && is_array( $args ) ) {
245 433 $atts = $args;
246 - extract( $args ); // @codingStandardsIgnoreLine
434 + extract( $args ); // @codingStandardsIgnoreLine
247 435 }
248 436
249 437 // Allow 3rd party plugin filter template file from their plugin.
250 438 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -359,9 +547,9 @@
359 547 // Return the Builder Template id.
360 548
361 549 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
362 550 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
363 - if ( $product_id ) {
551 + if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
364 552 return $product_id;
365 553 }
366 554 }
367 555
@@ -366,16 +554,16 @@
366 554 }
367 555
368 556 global $wpdb;
369 557 $cache_key = 'rtsb_prepared_product_id';
370 - $_post_id = wp_cache_get( $cache_key );
371 -
558 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
372 559 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
373 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
560 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
374 561 $_post_id = $wpdb->get_var(
375 562 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
376 563 );
377 - wp_cache_set( $cache_key, $_post_id, '', 12 * HOUR_IN_SECONDS );
564 + wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
565 + Cache::set_data_cache_key( $cache_key );
378 566 }
379 567
380 568 return $_post_id;
381 569 }
@@ -388,9 +576,8 @@
388 576 public static function get_product() {
389 577 global $product;
390 578
391 579 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
392 - // do_action( 'rtsb_before_product_template_render' );.
393 580 return $product;
394 581 }
395 582 $cache_key = 'prepared_product_for_preview';
396 583 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -411,8 +598,9 @@
411 598 *
412 599 * @return void
413 600 */
414 601 public static function doing_it_wrong( $function, $message, $version ) {
602 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
415 603 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
416 604 _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
417 605 }
418 606
@@ -430,8 +618,15 @@
430 618
431 619 return $pages;
432 620 }
433 621
622 + /**
623 + * Get section items
624 + *
625 + * @param string $section_id Section ID.
626 + *
627 + * @return array
628 + */
434 629 public static function get_section_items( $section_id ) {
435 630 if ( ! $section_id ) {
436 631 return [];
437 632 }
@@ -438,8 +633,16 @@
438 633
439 634 return DataModel::source()->get_option( $section_id, [] );
440 635 }
441 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 + */
442 645 public static function get_options( $section_id, $item_id ) {
443 646 if ( ! $section_id || ! $item_id ) {
444 647 return [];
445 648 }
@@ -450,11 +653,11 @@
450 653
451 654 /**
452 655 * Get options value with default value if options doesn't exist.
453 656 *
454 - * @param $group
455 - * @param $option_key
456 - * @param $default_value
657 + * @param array $group Group.
658 + * @param string $option_key Option key.
659 + * @param string $default_value Default value.
457 660 *
458 661 * @return mixed|string
459 662 */
460 663 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -466,32 +669,34 @@
466 669 return $group[ $option_key ];
467 670 }
468 671
469 672 /**
470 - * @param string $section_id
471 - * @param string $item_id
472 - * @param string $option_id
473 - * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value
474 - * @param null $type checkbox, multi_checkbox, number
673 + * Get option.
475 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 + *
476 681 * @return bool|int|mixed|null
477 682 */
478 683 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
479 684 $options = self::get_options( $section_id, $item_id );
480 685
481 - if ( $type === 'checkbox' ) {
686 + if ( 'checkbox' === $type ) {
482 687 if ( isset( $options[ $option_id ] ) ) {
483 - return $options[ $option_id ] === 'on';
688 + return 'on' === $options[ $option_id ];
484 689 }
485 690
486 691 return $default;
487 - } elseif ( $type === 'multi_checkbox' ) {
488 - return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] );
489 - } 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 ) {
490 695 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
491 696 }
492 697
493 - return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
698 + return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
494 699 }
495 700
496 701
497 702 /**
@@ -546,9 +751,9 @@
546 751
547 752 if ( strlen( $page_content ) > 0 ) {
548 753 // Search for an existing page with the specified page content (typically a shortcode).
549 754 $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
550 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
755 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
551 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}%" ) );
552 757 } else {
553 758 // Search for an existing page with the specified page slug.
554 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
@@ -618,8 +823,13 @@
618 823
619 824 return $page_id;
620 825 }
621 826
827 + /**
828 + * Get allowed HTML tags.
829 + *
830 + * @return array
831 + */
622 832 public static function get_kses_array() {
623 833 return [
624 834 'a' => [
625 835 'class' => [],
@@ -718,13 +928,14 @@
718 928 ];
719 929 }
720 930
721 931
722 - /*
932 + /**
723 933 * Escape output of wishlist icon
724 934 *
725 935 * @param string $data Data to escape.
726 - * @return string Escaped data
936 + *
937 + * @return void
727 938 */
728 939 public static function print_icon( $data ) {
729 940 /**
730 941 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -808,9 +1019,9 @@
808 1019 * @return mixed
809 1020 */
810 1021 public static function allowedHtml( $level = 'basic' ) {
811 1022 $allowed_html = [];
812 - // TODO:: Need Optimize.
1023 +
813 1024 switch ( $level ) {
814 1025 case 'basic':
815 1026 $allowed_html = [
816 1027 'b' => [
@@ -950,13 +1161,15 @@
950 1161 return $allowed_html;
951 1162 }
952 1163
953 1164 /**
954 - * Safe print a validated HTML tag.
1165 + * Safe get a validated HTML tag.
955 1166 *
956 - * @param string $tag
1167 + * @param string $tag HTML tag.
1168 + *
1169 + * @return string
957 1170 */
958 - public static function print_validated_html_tag( $tag ) {
1171 + public static function get_validated_html_tag( $tag ) {
959 1172 $allowed_html_wrapper_tags = [
960 1173 'a',
961 1174 'article',
962 1175 'aside',
@@ -976,12 +1189,23 @@
976 1189 'section',
977 1190 'span',
978 1191 ];
979 1192
980 - 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';
981 1194 }
982 1195
983 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 + /**
984 1208 * Insert Some array element
985 1209 *
986 1210 * @param [type] $key The elements will insert nearby this key.
987 1211 * @param [type] $main_array Original array.
@@ -1032,9 +1256,9 @@
1032 1256
1033 1257 $imgSize = [];
1034 1258
1035 1259 foreach ( $sizes as $key => $img ) {
1036 - $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1260 + $imgSize[ $key ] = esc_html( $key ) . " ({$img['width']}*{$img['height']})";
1037 1261 }
1038 1262
1039 1263 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1040 1264 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
@@ -1044,11 +1268,17 @@
1044 1268
1045 1269 /**
1046 1270 * Free Layouts
1047 1271 *
1272 + * @param string $layout Layout.
1273 + *
1048 1274 * @return array
1049 1275 */
1050 - 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 +
1051 1281 $layouts = [
1052 1282 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1053 1283 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1054 1284 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -1102,8 +1332,25 @@
1102 1332 }
1103 1333 self::$cache[ $cache_key ] = $terms;
1104 1334 return $terms;
1105 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 + }
1106 1353
1107 1354 /**
1108 1355 * Get all terms by attributes
1109 1356 *
@@ -1215,9 +1462,8 @@
1215 1462 if ( ! is_admin() ) {
1216 1463 return [];
1217 1464 }
1218 1465
1219 - // TODO:: Return Slug always true for all settings.
1220 1466 $term_list = [];
1221 1467 $args = [
1222 1468 'taxonomy' => $taxonomy,
1223 1469 'hide_empty' => false,
@@ -1287,9 +1533,9 @@
1287 1533
1288 1534 if ( $args['show_rating_count'] ) {
1289 1535 $count .= '<div class="rtsb-count">';
1290 1536 $count .= sprintf(
1291 - /* translators: %s is the number of reviews */
1537 + /* translators: %s is the number of reviews */
1292 1538 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1293 1539 $rating_count
1294 1540 );
1295 1541 $count .= '</div>';
@@ -1314,8 +1560,13 @@
1314 1560
1315 1561 self::print_html( $html, true );
1316 1562 }
1317 1563
1564 + /**
1565 + * Get product simple rating.
1566 + *
1567 + * @return void
1568 + */
1318 1569 public static function get_product_simple_rating_html() {
1319 1570 global $product;
1320 1571 $html = null;
1321 1572 $rating_count = $product->get_rating_count();
@@ -1427,9 +1678,42 @@
1427 1678
1428 1679 <?php
1429 1680 self::print_html( ob_get_clean() );
1430 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 + ?>
1431 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 +
1432 1716 /**
1433 1717 * Get Featured Image HTML.
1434 1718 *
1435 1719 * @param string $type Image type.
@@ -1532,9 +1816,9 @@
1532 1816
1533 1817 if ( ! $img_html ) {
1534 1818 $hwstring = image_hwstring( 160, 160 );
1535 1819 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1536 - $attr['class'] = 'default-img';
1820 + $attr['class'] = 'default-img ' . $hover_class;
1537 1821 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1538 1822 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1539 1823 $img_html = rtrim( "<img $hwstring" );
1540 1824
@@ -1625,9 +1909,9 @@
1625 1909 if ( empty( $control['value'] ) ) {
1626 1910 return '';
1627 1911 }
1628 1912 if ( is_array( $control['value'] ) ) {
1629 - $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
1630 1914 } else {
1631 1915 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1632 1916 }
1633 1917 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -1663,9 +1947,13 @@
1663 1947 public static function get_product_swatches( $type ) {
1664 1948 ?>
1665 1949 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1666 1950 <?php
1667 - 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 + }
1668 1956 ?>
1669 1957 </div>
1670 1958 <?php
1671 1959 }
@@ -1762,47 +2050,35 @@
1762 2050 public static function calculate_sale_percentage( $product = null ) {
1763 2051 if ( is_null( $product ) ) {
1764 2052 global $product;
1765 2053 }
1766 -
1767 2054 if ( ! $product instanceof WC_Product ) {
1768 2055 return '';
1769 2056 }
1770 -
1771 2057 if ( ! $product->is_on_sale() ) {
1772 2058 return '';
1773 2059 }
1774 -
1775 - $percentage = null;
1776 - $max_percentage = '';
1777 -
2060 + $max_percentage = 0;
1778 2061 if ( $product->is_type( 'simple' ) ) {
1779 - $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 + }
1780 2067 } elseif ( $product->is_type( 'variable' ) ) {
1781 - $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 ];
1782 2072
1783 - foreach ( $product->get_children() as $child_id ) {
1784 -
1785 - $variation = wc_get_product( $child_id );
1786 -
1787 - $price = $variation->get_regular_price();
1788 - $sale = $variation->get_sale_price();
1789 -
1790 - if ( 0 !== $price && ! empty( $sale ) ) {
1791 - $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 + }
1792 2077 }
1793 -
1794 - if ( $percentage > $max_percentage ) {
1795 - $max_percentage = $percentage;
1796 - }
1797 2078 }
1798 2079 }
1799 -
1800 - if ( $max_percentage > 0 ) {
1801 - return round( $max_percentage );
1802 - }
1803 -
1804 - return 0;
2080 + return $max_percentage > 0 ? round( $max_percentage ) : 0;
1805 2081 }
1806 2082
1807 2083 /**
1808 2084 * Pagination
@@ -1819,15 +2095,15 @@
1819 2095
1820 2096 global $paged;
1821 2097
1822 2098 if ( is_front_page() ) {
1823 - $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
1824 2100 } else {
1825 - $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
1826 2102 }
1827 2103
1828 2104 if ( empty( $paged ) ) {
1829 - $paged = 1;
2105 + $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1830 2106 }
1831 2107
1832 2108 if ( '' === $pages ) {
1833 2109 global $wp_query;
@@ -1985,10 +2261,8 @@
1985 2261 } else {
1986 2262 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
1987 2263 }
1988 2264
1989 - $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
1990 -
1991 2265 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
1992 2266 $html .= $cart_html;
1993 2267 } else {
1994 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -1993,9 +2267,11 @@
1993 2267 } else {
1994 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
1995 2269 }
1996 2270
1997 - $html .= '</' . esc_attr( $wrapper ) . '>';
2271 + if ( ! empty( $html ) ) {
2272 + $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2273 + }
1998 2274
1999 2275 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2000 2276 }
2001 2277
@@ -2096,9 +2372,9 @@
2096 2372 $link['social_network'] = 'Facebook';
2097 2373 $link['social_action'] = 'Share';
2098 2374 break;
2099 2375 case 'twitter':
2100 - $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'] );
2101 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>';
2102 2378 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2103 2379 $link['social_network'] = 'Twitter';
2104 2380 $link['social_action'] = 'Tweet';
@@ -2132,9 +2408,9 @@
2132 2408 $link['social_action'] = 'Share';
2133 2409 break;
2134 2410 case 'reddit':
2135 2411 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2136 - $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>';
2137 2413 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2138 2414 $link['social_network'] = 'Reddit';
2139 2415 $link['social_action'] = 'Share';
2140 2416 break;
@@ -2222,27 +2498,61 @@
2222 2498
2223 2499 /**
2224 2500 * Social Share Platforms.
2225 2501 *
2226 - * @return mixed|null
2502 + * @param string $module Module.
2503 + *
2504 + * @return true|void
2227 2505 */
2228 2506 public static function is_module_active( $module ) {
2229 - $modulelist = ModuleList::instance()->get_data();
2507 + $modulelist = self::get_modules_list();
2508 +
2230 2509 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2231 2510 return true;
2232 2511 }
2233 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 + }
2234 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 +
2235 2545 /***
2236 2546 * Save Settings data
2237 2547 *
2238 - * @param $section_id
2239 - * @param $block_id
2240 - * @param $rawOptions
2548 + * @param string $section_id Section ID.
2549 + * @param string $block_id Block ID.
2550 + * @param array $rawOptions Raw Options.
2241 2551 *
2242 2552 * @return array
2243 2553 */
2244 - 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
2245 2555 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2246 2556 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2247 2557 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2248 2558 $results = [
@@ -2268,9 +2578,9 @@
2268 2578 $fields = [];
2269 2579 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2270 2580 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2271 2581 }
2272 -
2582 + do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2273 2583 if ( empty( $fields ) ) {
2274 2584 if ( isset( $rawOptions['active'] ) ) {
2275 2585 $changed = true;
2276 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2276,21 +2586,49 @@
2276 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2277 2587 }
2278 2588 } else {
2279 2589 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2280 - if ( $raw_option_key === 'active' ) {
2590 + if ( 'active' === $raw_option_key ) {
2281 2591 $changed = true;
2282 - $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
2283 2593 } else {
2284 2594 if ( isset( $fields[ $raw_option_key ] ) ) {
2285 2595 $field = $fields[ $raw_option_key ];
2286 - if ( $field['type'] === 'switch' ) {
2596 + if ( 'switch' === $field['type'] ) {
2287 2597 $value = 'on' === $raw_value ? 'on' : '';
2288 2598 } elseif ( 'repeaters' === $field['type'] ) {
2289 - $value = stripslashes_deep( $raw_value );
2599 + $the_value = [];
2600 + $rep_title = [];
2601 + if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2602 + foreach ( $raw_value as $key => $value ) {
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 ?? '';
2609 + if ( in_array( $cr, $rep_title, true ) ) {
2610 + continue;
2611 + }
2612 + $rep_title[] = $cr;
2613 + $the_value[] = $the_value_decoded;
2614 + }
2615 + } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2616 + $the_value = $raw_value;
2617 + }
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 + }
2290 2629 } else {
2291 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2292 -
2630 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2293 2631 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2294 2632 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2295 2633 $value = array_map( $field['sanitize_fn'], $raw_value );
2296 2634 } else {
@@ -2312,9 +2650,9 @@
2312 2650 $value = sanitize_text_field( $raw_value );
2313 2651 }
2314 2652 }
2315 2653 }
2316 - $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
2317 2655 $changed = true;
2318 2656 }
2319 2657 }
2320 2658 }
@@ -2334,27 +2672,15 @@
2334 2672
2335 2673 return $results;
2336 2674 }
2337 2675
2338 - /***
2339 - * @param $meta_key
2340 - * @param $meta_value
2676 + /**
2677 + * Count products by taxonomies.
2341 2678 *
2342 - * @return mixed|void
2679 + * @param array $terms Terms.
2680 + * @param string $taxonomy Taxonomy.
2343 2681 *
2344 - *
2345 - * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2346 - * if( ! $meta_key || ! $meta_value ){
2347 - * return;
2348 - * }
2349 - * global $wpdb;
2350 - * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2351 - * $the_products = $wpdb->get_col( $prepare_guery );
2352 - * if( count( $the_products ) > 0 ){
2353 - * return $the_products[0];
2354 - * }
2355 - * return;
2356 - * }
2682 + * @return int|void
2357 2683 */
2358 2684 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2359 2685 if ( empty( $terms ) || ! is_array( $terms ) ) {
2360 2686 return;
@@ -2366,8 +2692,10 @@
2366 2692 ];
2367 2693
2368 2694 if ( 'product_cat' === $taxonomy ) {
2369 2695 $args['product_category_id'] = $terms;
2696 + } elseif ( 'product_brand' === $taxonomy ) {
2697 + $args['product_brand_id'] = $terms;
2370 2698 } else {
2371 2699 $args['product_tag_id'] = $terms;
2372 2700 }
2373 2701
@@ -2376,8 +2704,61 @@
2376 2704 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2377 2705 }
2378 2706
2379 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 + /**
2380 2761 * Check if product filters widget has ajax.
2381 2762 *
2382 2763 * @param string $page Page name.
2383 2764 *
@@ -2448,22 +2829,76 @@
2448 2829 if ( ! is_admin() ) {
2449 2830 return [];
2450 2831 }
2451 2832
2452 - $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
2453 2834
2454 2835 if ( isset( self::$cache[ $cache_key ] ) ) {
2455 2836 return self::$cache[ $cache_key ];
2456 2837 }
2838 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2839 + if ( ! $results ) {
2840 + global $wpdb;
2841 + $sql = "SELECT t.term_id, t.name
2842 + FROM {$wpdb->terms} t
2843 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2844 + WHERE tt.taxonomy = 'product_cat'";
2457 2845
2458 - $results = wp_cache_get( $cache_key );
2846 + if ( $search_query ) {
2847 + $sql .= ' AND t.name LIKE %s';
2848 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2849 + } else {
2850 + $prepared_sql = $sql; // No need for placeholders.
2851 + }
2459 2852
2853 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2854 + // Cache the results in the object cache for future use.
2855 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2856 + Cache::set_data_cache_key( $cache_key );
2857 + }
2858 +
2859 + $cats = [];
2860 + if ( ! is_array( $results ) && ! count( $results ) ) {
2861 + return $cats;
2862 + }
2863 + foreach ( $results as $row ) {
2864 + $category_id = $row->term_id;
2865 + $category_title = $row->name;
2866 +
2867 + $cats[] = [
2868 + 'value' => $category_id,
2869 + 'label' => $category_title,
2870 + ];
2871 + }
2872 +
2873 + // Cache the results in a static variable for the next call.
2874 + self::$cache[ $cache_key ] = $cats;
2875 + return $cats;
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 + }
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' );
2460 2895 if ( ! $results ) {
2461 2896 global $wpdb;
2462 2897 $sql = "SELECT t.term_id, t.name
2463 2898 FROM {$wpdb->terms} t
2464 2899 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2465 - WHERE tt.taxonomy = 'product_cat'";
2900 + WHERE tt.taxonomy = 'product_tag'";
2466 2901
2467 2902 if ( $search_query ) {
2468 2903 $sql .= ' AND t.name LIKE %s';
2469 2904 $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
@@ -2472,9 +2907,10 @@
2472 2907 }
2473 2908
2474 2909 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2475 2910 // Cache the results in the object cache for future use.
2476 - wp_cache_set( $cache_key, $results ); // Adjust the expiration time as needed.
2911 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2912 + Cache::set_data_cache_key( $cache_key );
2477 2913 }
2478 2914
2479 2915 $cats = [];
2480 2916 if ( ! is_array( $results ) && ! count( $results ) ) {
@@ -2553,8 +2989,9 @@
2553 2989 /**
2554 2990 * Get Post Types.
2555 2991 *
2556 2992 * @param string $search_query Search query.
2993 + * @param array $args Arguments.
2557 2994 *
2558 2995 * @return array
2559 2996 */
2560 2997 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2565,9 +3002,8 @@
2565 3002 'post_type' => 'any',
2566 3003 'posts_per_page' => 20,
2567 3004 'orderby' => 'ID',
2568 3005 'order' => 'DESC',
2569 - // 'suppress_filters' => false, // WPML Support, Remove Others Languages.
2570 3006 ]
2571 3007 );
2572 3008
2573 3009 if ( 'archive' !== $args['post_type'] ) {
@@ -2578,11 +3014,12 @@
2578 3014 $query_results = get_posts( $args );
2579 3015 foreach ( $query_results as $post ) {
2580 3016 $posts[] = [
2581 3017 'value' => $post->ID,
2582 - 'label' => $post->post_title . ' (' . $post->ID . ')',
3018 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2583 3019 ];
2584 3020 }
3021 +
2585 3022 if ( $search_query ) {
2586 3023 if ( strpos( '-error 404', $search_query ) ) {
2587 3024 $posts[] = [
2588 3025 'value' => 'error',
@@ -2589,12 +3026,14 @@
2589 3026 'label' => __( '404 Error', 'shopbuilder' ),
2590 3027 ];
2591 3028 }
2592 3029 } else {
2593 - $posts[] = [
2594 - 'value' => 'error',
2595 - 'label' => __( '404 Error', 'shopbuilder' ),
2596 - ];
3030 + if ( 'page' === $args['post_type'] ) {
3031 + $posts[] = [
3032 + 'value' => 'error',
3033 + 'label' => __( '404 Error', 'shopbuilder' ),
3034 + ];
3035 + }
2597 3036 }
2598 3037 } else {
2599 3038 $posts = self::get_registered_post_types( $search_query );
2600 3039 }
@@ -2622,9 +3061,9 @@
2622 3061 public static function renderView( $viewName, $args = [], $return = false ) {
2623 3062 $viewName = str_replace( '.', '/', $viewName );
2624 3063
2625 3064 if ( ! empty( $args ) && is_array( $args ) ) {
2626 - extract( $args );
3065 + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
2627 3066 }
2628 3067
2629 3068 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2630 3069
@@ -2646,9 +3085,9 @@
2646 3085
2647 3086 /**
2648 3087 * Best selling product query.
2649 3088 *
2650 - * @param int $minimum_sale Minimum sale/
3089 + * @param int $minimum_sale Minimum sale.
2651 3090 * @param int $limit Total limit.
2652 3091 *
2653 3092 * @return array|mixed
2654 3093 */
@@ -2706,12 +3145,14 @@
2706 3145
2707 3146 if ( isset( self::$cache[ $cache_key ] ) ) {
2708 3147 return self::$cache[ $cache_key ];
2709 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' ) );
2710 3154
2711 - // Get the product publish date.
2712 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2713 -
2714 3155 // Calculate the difference in days.
2715 3156 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2716 3157
2717 3158 // Check if the product is considered new.
@@ -2739,12 +3180,9 @@
2739 3180
2740 3181 /**
2741 3182 * Checks if a feature is disabled for guest users based on the provided option.
2742 3183 *
2743 - * @param string $option The option to retrieve from the item.
2744 - * @param mixed $default The default value if the option is not found.
2745 - *
2746 - * @return bool
3184 + * @return void
2747 3185 */
2748 3186 public static function woocommerce_output_all_notices() {
2749 3187 if ( function_exists( 'wc_print_notices' ) ) :
2750 3188 echo '<div class="rtsb-notice">';
@@ -2846,8 +3284,68 @@
2846 3284 ];
2847 3285 }
2848 3286
2849 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 + /**
2850 3348 * Flushes rewrite rules.
2851 3349 *
2852 3350 * @return void
2853 3351 */
@@ -2887,12 +3385,18 @@
2887 3385
2888 3386 /**
2889 3387 * Social Share Platforms.
2890 3388 *
2891 - * @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
2892 3397 */
2893 3398 public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
2894 - // $options = self::get_options( $section_id, $block_id );
2895 3399 $modules = DataModel::source()->get_option( $section_id, [], false );
2896 3400 if ( empty( $modules[ $block_id ] ) ) {
2897 3401 return;
2898 3402 }
@@ -2913,9 +3417,10 @@
2913 3417 return;
2914 3418 }
2915 3419 $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
2916 3420 $options[ $option_key ] = wp_json_encode( $repeater_options );
2917 - self::set_options( $section_id, $block_id, $options );
3421 + $modules[ $block_id ] = $options;
3422 + DataModel::source()->set_option( $section_id, $modules );
2918 3423 }
2919 3424
2920 3425
2921 3426 /**
@@ -2931,6 +3436,673 @@
2931 3436 );
2932 3437 return $ids;
2933 3438 }
2934 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' );
2935 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 );
4107 + }
2936 4108 }