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 +1647 -174 2.1.43.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 */
@@ -45,12 +98,118 @@
45 98 return false;
46 99 }
47 100
48 101 /**
49 - * @param $plugin_slug
102 + * Get nonce.
50 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 + */
129 + public static function get_nonce() {
130 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
131 + return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
132 + }
133 +
134 + /**
135 + * Set Session
136 + *
137 + * @param string $name Name.
138 + * @param mixed $value Value.
139 + */
140 + public static function setSession( $name, $value ) {
141 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
142 + session_start();
143 + $_SESSION[ $name ] = $value;
144 + } else {
145 + $_SESSION[ $name ] = $value;
146 + }
147 + }
148 +
149 + /**
150 + * Get Cookie or Session
151 + *
152 + * @param string $name Name.
153 + *
51 154 * @return bool
52 155 */
156 + public static function getSession( $name ) {
157 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
158 + session_start();
159 + }
160 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
161 + }
162 + /**
163 + * Remove Session Variable
164 + *
165 + * @param string $name The name of the session variable to remove.
166 + */
167 + public static function removeSession( $name ) {
168 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
169 + session_start();
170 + unset( $_SESSION[ $name ] );
171 + } else {
172 + unset( $_SESSION[ $name ] );
173 + }
174 + }
175 + /**
176 + * Gets the current timestamp in WordPress timezone.
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 + *
210 + * @return bool
211 + */
53 212 public static function check_plugin_installed( $plugin_slug ): bool {
54 213 $installed_plugins = get_plugins();
55 214
56 215 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
@@ -56,10 +215,12 @@
56 215 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
57 216 }
58 217
59 218 /**
60 - * @param $plugin_slug
219 + * Check if a plugin is active.
61 220 *
221 + * @param string $plugin_slug Plugin slug.
222 + *
62 223 * @return bool
63 224 */
64 225 public static function check_plugin_active( $plugin_slug ): bool {
65 226 if ( is_plugin_active( $plugin_slug ) ) {
@@ -67,21 +228,71 @@
67 228 }
68 229
69 230 return false;
70 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' );
71 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 +
72 270 /**
73 - * @param $data
271 + * Stripslashes value.
74 272 *
273 + * @param mixed $data Data.
274 + *
75 275 * @return mixed|string
76 276 */
77 277 public static function stripslashes_value( $data ) {
78 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
79 - return stripslashes( $data );
80 - } elseif ( is_array( $data ) ) {
278 + if ( is_array( $data ) ) {
81 279 foreach ( $data as $key => $value ) {
82 280 $data[ $key ] = self::stripslashes_value( $value );
83 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 + }
84 295 }
85 296
86 297 return $data;
87 298 }
@@ -86,10 +297,12 @@
86 297 return $data;
87 298 }
88 299
89 300 /**
90 - * @param $string
301 + * Multiselect settings field value
91 302 *
303 + * @param string $string String.
304 + *
92 305 * @return array
93 306 */
94 307 public static function multiselect_settings_field_value( $string ) {
95 308
@@ -109,8 +322,10 @@
109 322 return $values;
110 323 }
111 324
112 325 /**
326 + * Check if is block theme
327 + *
113 328 * @return bool
114 329 */
115 330 public static function check_is_block_theme(): bool {
116 331 global $wp_version;
@@ -118,16 +333,19 @@
118 333 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
119 334 }
120 335
121 336 /**
337 + * Locate template
338 + *
122 339 * @param string $template_name Template name.
123 340 * @param string $template_path Template path. (default: '').
124 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
341 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
125 342 *
126 343 * @return mixed|void
127 344 */
128 345 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
129 - $template_name = $template_name . '.php';
346 + $template_name = self::sanitize_file_name( $template_name ) . '.php';
347 +
130 348 if ( ! $template_path ) {
131 349 $template_path = rtsb()->get_template_path();
132 350 }
133 351 if ( ! $plugin_path ) {
@@ -149,12 +367,12 @@
149 367 )
150 368 );
151 369
152 370 if ( ! $located ) {
153 - 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 ) ) {
154 374 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
155 - } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
156 - return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
157 375 }
158 376 }
159 377
160 378 /**
@@ -170,8 +388,19 @@
170 388 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
171 389 }
172 390
173 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 + /**
174 403 * Template Content
175 404 *
176 405 * @param string $template_name Template name.
177 406 * @param array $args Arguments. (default: array).
@@ -180,11 +409,20 @@
180 409 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
181 410 *
182 411 * @return false|string|void
183 412 */
184 - public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
185 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
186 -
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 + }
187 425 if ( ! file_exists( $located ) ) {
188 426 // translators: %s template.
189 427 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
190 428
@@ -192,9 +430,9 @@
192 430 }
193 431
194 432 if ( ! empty( $args ) && is_array( $args ) ) {
195 433 $atts = $args;
196 - extract( $args ); // @codingStandardsIgnoreLine
434 + extract( $args ); // @codingStandardsIgnoreLine
197 435 }
198 436
199 437 // Allow 3rd party plugin filter template file from their plugin.
200 438 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -309,9 +547,9 @@
309 547 // Return the Builder Template id.
310 548
311 549 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
312 550 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
313 - if ( $product_id ) {
551 + if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
314 552 return $product_id;
315 553 }
316 554 }
317 555
@@ -316,15 +554,16 @@
316 554 }
317 555
318 556 global $wpdb;
319 557 $cache_key = 'rtsb_prepared_product_id';
320 - $_post_id = wp_cache_get( $cache_key );
321 -
558 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
322 559 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
560 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
323 561 $_post_id = $wpdb->get_var(
324 562 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
325 563 );
326 - 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 );
327 566 }
328 567
329 568 return $_post_id;
330 569 }
@@ -337,9 +576,8 @@
337 576 public static function get_product() {
338 577 global $product;
339 578
340 579 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
341 - do_action( 'rtsb_before_product_template_render' );
342 580 return $product;
343 581 }
344 582 $cache_key = 'prepared_product_for_preview';
345 583 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -344,9 +582,8 @@
344 582 $cache_key = 'prepared_product_for_preview';
345 583 if ( isset( self::$cache[ $cache_key ] ) ) {
346 584 return self::$cache[ $cache_key ];
347 585 }
348 -
349 586 $product = wc_get_product( self::get_prepared_product_id() );
350 587 self::$cache[ $cache_key ] = $product;
351 588 do_action( 'rtsb_before_product_template_render' );
352 589 return $product;
@@ -361,10 +598,11 @@
361 598 *
362 599 * @return void
363 600 */
364 601 public static function doing_it_wrong( $function, $message, $version ) {
602 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
365 603 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
366 - _doing_it_wrong( $function, $message, $version );
604 + _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
367 605 }
368 606
369 607 /**
370 608 * @return array
@@ -380,8 +618,15 @@
380 618
381 619 return $pages;
382 620 }
383 621
622 + /**
623 + * Get section items
624 + *
625 + * @param string $section_id Section ID.
626 + *
627 + * @return array
628 + */
384 629 public static function get_section_items( $section_id ) {
385 630 if ( ! $section_id ) {
386 631 return [];
387 632 }
@@ -388,8 +633,16 @@
388 633
389 634 return DataModel::source()->get_option( $section_id, [] );
390 635 }
391 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 + */
392 645 public static function get_options( $section_id, $item_id ) {
393 646 if ( ! $section_id || ! $item_id ) {
394 647 return [];
395 648 }
@@ -400,11 +653,11 @@
400 653
401 654 /**
402 655 * Get options value with default value if options doesn't exist.
403 656 *
404 - * @param $group
405 - * @param $option_key
406 - * @param $default_value
657 + * @param array $group Group.
658 + * @param string $option_key Option key.
659 + * @param string $default_value Default value.
407 660 *
408 661 * @return mixed|string
409 662 */
410 663 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -416,32 +669,34 @@
416 669 return $group[ $option_key ];
417 670 }
418 671
419 672 /**
420 - * @param string $section_id
421 - * @param string $item_id
422 - * @param string $option_id
423 - * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value
424 - * @param null $type checkbox, multi_checkbox, number
673 + * Get option.
425 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 + *
426 681 * @return bool|int|mixed|null
427 682 */
428 683 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
429 684 $options = self::get_options( $section_id, $item_id );
430 685
431 - if ( $type === 'checkbox' ) {
686 + if ( 'checkbox' === $type ) {
432 687 if ( isset( $options[ $option_id ] ) ) {
433 - return $options[ $option_id ] === 'on';
688 + return 'on' === $options[ $option_id ];
434 689 }
435 690
436 691 return $default;
437 - } elseif ( $type === 'multi_checkbox' ) {
438 - return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] );
439 - } 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 ) {
440 695 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
441 696 }
442 697
443 - return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
698 + return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
444 699 }
445 700
446 701
447 702 /**
@@ -495,13 +750,14 @@
495 750 }
496 751
497 752 if ( strlen( $page_content ) > 0 ) {
498 753 // Search for an existing page with the specified page content (typically a shortcode).
499 - $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
754 + $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
755 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
500 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}%" ) );
501 757 } else {
502 758 // Search for an existing page with the specified page slug.
503 - $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 ) );
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
504 760 }
505 761
506 762 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
507 763
@@ -523,12 +779,12 @@
523 779
524 780 // Search for a matching valid trashed page.
525 781 if ( strlen( $page_content ) > 0 ) {
526 782 // Search for an existing page with the specified page content (typically a shortcode).
527 - $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) );
783 + $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
528 784 } else {
529 785 // Search for an existing page with the specified page slug.
530 - $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) );
786 + $trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
531 787 }
532 788
533 789 if ( $trashed_page_found ) {
534 790 $page_id = $trashed_page_found;
@@ -567,8 +823,13 @@
567 823
568 824 return $page_id;
569 825 }
570 826
827 + /**
828 + * Get allowed HTML tags.
829 + *
830 + * @return array
831 + */
571 832 public static function get_kses_array() {
572 833 return [
573 834 'a' => [
574 835 'class' => [],
@@ -667,13 +928,14 @@
667 928 ];
668 929 }
669 930
670 931
671 - /*
932 + /**
672 933 * Escape output of wishlist icon
673 934 *
674 935 * @param string $data Data to escape.
675 - * @return string Escaped data
936 + *
937 + * @return void
676 938 */
677 939 public static function print_icon( $data ) {
678 940 /**
679 941 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -757,9 +1019,9 @@
757 1019 * @return mixed
758 1020 */
759 1021 public static function allowedHtml( $level = 'basic' ) {
760 1022 $allowed_html = [];
761 - // TODO:: Need Optimize.
1023 +
762 1024 switch ( $level ) {
763 1025 case 'basic':
764 1026 $allowed_html = [
765 1027 'b' => [
@@ -899,13 +1161,15 @@
899 1161 return $allowed_html;
900 1162 }
901 1163
902 1164 /**
903 - * Safe print a validated HTML tag.
1165 + * Safe get a validated HTML tag.
904 1166 *
905 - * @param string $tag
1167 + * @param string $tag HTML tag.
1168 + *
1169 + * @return string
906 1170 */
907 - public static function print_validated_html_tag( $tag ) {
1171 + public static function get_validated_html_tag( $tag ) {
908 1172 $allowed_html_wrapper_tags = [
909 1173 'a',
910 1174 'article',
911 1175 'aside',
@@ -925,12 +1189,23 @@
925 1189 'section',
926 1190 'span',
927 1191 ];
928 1192
929 - 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';
930 1194 }
931 1195
932 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 + /**
933 1208 * Insert Some array element
934 1209 *
935 1210 * @param [type] $key The elements will insert nearby this key.
936 1211 * @param [type] $main_array Original array.
@@ -981,9 +1256,9 @@
981 1256
982 1257 $imgSize = [];
983 1258
984 1259 foreach ( $sizes as $key => $img ) {
985 - $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1260 + $imgSize[ $key ] = esc_html( $key ) . " ({$img['width']}*{$img['height']})";
986 1261 }
987 1262
988 1263 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
989 1264 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
@@ -993,11 +1268,17 @@
993 1268
994 1269 /**
995 1270 * Free Layouts
996 1271 *
1272 + * @param string $layout Layout.
1273 + *
997 1274 * @return array
998 1275 */
999 - 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 +
1000 1281 $layouts = [
1001 1282 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1002 1283 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1003 1284 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -1030,9 +1311,14 @@
1030 1311 if ( isset( self::$cache[ $cache_key ] ) ) {
1031 1312 return self::$cache[ $cache_key ];
1032 1313 }
1033 1314
1034 - $termList = get_terms( [ $taxonomy ], [ 'hide_empty' => 0 ] );
1315 + $termList = get_terms(
1316 + [
1317 + 'taxonomy' => $taxonomy,
1318 + 'hide_empty' => false,
1319 + ]
1320 + );
1035 1321
1036 1322 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
1037 1323 if ( $placeholder ) {
1038 1324 $terms = [
@@ -1046,8 +1332,25 @@
1046 1332 }
1047 1333 self::$cache[ $cache_key ] = $terms;
1048 1334 return $terms;
1049 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 + }
1050 1353
1051 1354 /**
1052 1355 * Get all terms by attributes
1053 1356 *
@@ -1061,9 +1364,9 @@
1061 1364
1062 1365 if ( $attributes ) {
1063 1366 foreach ( $attributes as $tax ) {
1064 1367 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1065 - $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ), 'orderby=name&hide_empty=0' );
1368 + $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1066 1369 }
1067 1370 }
1068 1371 }
1069 1372
@@ -1159,9 +1462,8 @@
1159 1462 if ( ! is_admin() ) {
1160 1463 return [];
1161 1464 }
1162 1465
1163 - // TODO:: Return Slug always true for all settings.
1164 1466 $term_list = [];
1165 1467 $args = [
1166 1468 'taxonomy' => $taxonomy,
1167 1469 'hide_empty' => false,
@@ -1231,9 +1533,9 @@
1231 1533
1232 1534 if ( $args['show_rating_count'] ) {
1233 1535 $count .= '<div class="rtsb-count">';
1234 1536 $count .= sprintf(
1235 - /* translators: %s is the number of reviews */
1537 + /* translators: %s is the number of reviews */
1236 1538 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1237 1539 $rating_count
1238 1540 );
1239 1541 $count .= '</div>';
@@ -1259,8 +1561,28 @@
1259 1561 self::print_html( $html, true );
1260 1562 }
1261 1563
1262 1564 /**
1565 + * Get product simple rating.
1566 + *
1567 + * @return void
1568 + */
1569 + public static function get_product_simple_rating_html() {
1570 + global $product;
1571 + $html = null;
1572 + $rating_count = $product->get_rating_count();
1573 + $average_rating = $product->get_average_rating();
1574 + $html .= '<div class="product-rating">';
1575 + $html .= wc_get_rating_html( $average_rating, $rating_count );
1576 + $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1577 + $html .= '</div>';
1578 +
1579 + self::print_html( $html, true );
1580 + }
1581 +
1582 +
1583 +
1584 + /**
1263 1585 * Text truncation.
1264 1586 *
1265 1587 * @param string $text_to_truncate Text.
1266 1588 * @param int $limit Limit.
@@ -1302,8 +1624,14 @@
1302 1624 *
1303 1625 * @return void
1304 1626 */
1305 1627 public static function get_badge_html( $text, $class = 'fill' ) {
1628 + if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1629 + do_action( 'rtsb/modules/product_badges/frontend/display' );
1630 +
1631 + return;
1632 + }
1633 +
1306 1634 if ( empty( $text ) ) {
1307 1635 return;
1308 1636 }
1309 1637 ob_start();
@@ -1310,10 +1638,9 @@
1310 1638 ?>
1311 1639
1312 1640 <ul class="rtsb-promotion-list">
1313 1641 <li class="rtsb-promotion-list-item">
1314 - <span
1315 - class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1642 + <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1316 1643 </li>
1317 1644 </ul>
1318 1645
1319 1646 <?php
@@ -1351,9 +1678,42 @@
1351 1678
1352 1679 <?php
1353 1680 self::print_html( ob_get_clean() );
1354 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 + ?>
1355 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 +
1356 1716 /**
1357 1717 * Get Featured Image HTML.
1358 1718 *
1359 1719 * @param string $type Image type.
@@ -1456,9 +1816,9 @@
1456 1816
1457 1817 if ( ! $img_html ) {
1458 1818 $hwstring = image_hwstring( 160, 160 );
1459 1819 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1460 - $attr['class'] = 'default-img';
1820 + $attr['class'] = 'default-img ' . $hover_class;
1461 1821 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1462 1822 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1463 1823 $img_html = rtrim( "<img $hwstring" );
1464 1824
@@ -1549,9 +1909,9 @@
1549 1909 if ( empty( $control['value'] ) ) {
1550 1910 return '';
1551 1911 }
1552 1912 if ( is_array( $control['value'] ) ) {
1553 - $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
1554 1914 } else {
1555 1915 $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1556 1916 }
1557 1917 if ( isset( self::$cache[ $cache_key ] ) ) {
@@ -1587,9 +1947,13 @@
1587 1947 public static function get_product_swatches( $type ) {
1588 1948 ?>
1589 1949 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1590 1950 <?php
1591 - 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 + }
1592 1956 ?>
1593 1957 </div>
1594 1958 <?php
1595 1959 }
@@ -1686,43 +2050,35 @@
1686 2050 public static function calculate_sale_percentage( $product = null ) {
1687 2051 if ( is_null( $product ) ) {
1688 2052 global $product;
1689 2053 }
1690 -
1691 2054 if ( ! $product instanceof WC_Product ) {
1692 2055 return '';
1693 2056 }
1694 -
1695 - $percentage = null;
1696 - $max_percentage = '';
1697 -
2057 + if ( ! $product->is_on_sale() ) {
2058 + return '';
2059 + }
2060 + $max_percentage = 0;
1698 2061 if ( $product->is_type( 'simple' ) ) {
1699 - $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 + }
1700 2067 } elseif ( $product->is_type( 'variable' ) ) {
1701 - $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 ];
1702 2072
1703 - foreach ( $product->get_children() as $child_id ) {
1704 -
1705 - $variation = wc_get_product( $child_id );
1706 -
1707 - $price = $variation->get_regular_price();
1708 - $sale = $variation->get_sale_price();
1709 -
1710 - if ( 0 !== $price && ! empty( $sale ) ) {
1711 - $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 + }
1712 2077 }
1713 -
1714 - if ( $percentage > $max_percentage ) {
1715 - $max_percentage = $percentage;
1716 - }
1717 2078 }
1718 2079 }
1719 -
1720 - if ( $max_percentage > 0 ) {
1721 - return round( $max_percentage );
1722 - }
1723 -
1724 - return 0;
2080 + return $max_percentage > 0 ? round( $max_percentage ) : 0;
1725 2081 }
1726 2082
1727 2083 /**
1728 2084 * Pagination
@@ -1733,21 +2089,21 @@
1733 2089 *
1734 2090 * @return string
1735 2091 */
1736 2092 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
1737 - $html = null;
1738 - $showitems = ( $range * 2 ) + 1;
2093 + $html = null;
2094 + $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
1739 2095
1740 2096 global $paged;
1741 2097
1742 2098 if ( is_front_page() ) {
1743 - $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
1744 2100 } else {
1745 - $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
1746 2102 }
1747 2103
1748 2104 if ( empty( $paged ) ) {
1749 - $paged = 1;
2105 + $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1750 2106 }
1751 2107
1752 2108 if ( '' === $pages ) {
1753 2109 global $wp_query;
@@ -1764,8 +2120,9 @@
1764 2120
1765 2121 if ( $ajax ) {
1766 2122 $ajaxClass = ' rtsb-pagination-ajax';
1767 2123 $dataAttr = "data-paged='1'";
2124 + $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
1768 2125 }
1769 2126
1770 2127 if ( 1 !== $pages ) {
1771 2128 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
@@ -1770,29 +2127,29 @@
1770 2127 if ( 1 !== $pages ) {
1771 2128 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
1772 2129 $html .= '<ul class="pagination-list">';
1773 2130
1774 - if ( $paged > 2 && $paged > $range + 1 && $showitems < $pages ) {
2131 + if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
1775 2132 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
1776 2133 }
1777 2134
1778 - if ( $paged > 1 && $showitems < $pages ) {
2135 + if ( $paged > 1 && $visible_range < $pages ) {
1779 2136 $p = $paged - 1;
1780 2137 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
1781 2138 }
1782 2139
1783 2140 for ( $i = 1; $i <= $pages; $i++ ) {
1784 - if ( 1 != $pages && ( ! ( $i >= $paged + $range + 1 || $i <= $paged - $range - 1 ) || $pages <= $showitems ) ) {
2141 + if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
1785 2142 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
1786 2143 }
1787 2144 }
1788 2145
1789 - if ( $paged < $pages && $showitems < $pages ) {
2146 + if ( $paged < $pages && $visible_range < $pages ) {
1790 2147 $p = $paged + 1;
1791 2148 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
1792 2149 }
1793 2150
1794 - if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $showitems < $pages ) {
2151 + if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
1795 2152 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
1796 2153 }
1797 2154
1798 2155 $html .= '</ul>';
@@ -1904,10 +2261,8 @@
1904 2261 } else {
1905 2262 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
1906 2263 }
1907 2264
1908 - $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
1909 -
1910 2265 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
1911 2266 $html .= $cart_html;
1912 2267 } else {
1913 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -1912,9 +2267,11 @@
1912 2267 } else {
1913 2268 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
1914 2269 }
1915 2270
1916 - $html .= '</' . esc_attr( $wrapper ) . '>';
2271 + if ( ! empty( $html ) ) {
2272 + $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2273 + }
1917 2274
1918 2275 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
1919 2276 }
1920 2277
@@ -2015,9 +2372,9 @@
2015 2372 $link['social_network'] = 'Facebook';
2016 2373 $link['social_action'] = 'Share';
2017 2374 break;
2018 2375 case 'twitter':
2019 - $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'] );
2020 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>';
2021 2378 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2022 2379 $link['social_network'] = 'Twitter';
2023 2380 $link['social_action'] = 'Tweet';
@@ -2051,9 +2408,9 @@
2051 2408 $link['social_action'] = 'Share';
2052 2409 break;
2053 2410 case 'reddit':
2054 2411 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2055 - $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>';
2056 2413 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2057 2414 $link['social_network'] = 'Reddit';
2058 2415 $link['social_action'] = 'Share';
2059 2416 break;
@@ -2141,33 +2498,65 @@
2141 2498
2142 2499 /**
2143 2500 * Social Share Platforms.
2144 2501 *
2145 - * @return mixed|null
2502 + * @param string $module Module.
2503 + *
2504 + * @return true|void
2146 2505 */
2147 2506 public static function is_module_active( $module ) {
2148 - $modulelist = ModuleList::instance()->get_data();
2507 + $modulelist = self::get_modules_list();
2508 +
2149 2509 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2150 2510 return true;
2151 2511 }
2152 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 + }
2153 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();
2154 2539
2540 + if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2541 + return true;
2542 + }
2543 + }
2544 +
2155 2545 /***
2156 2546 * Save Settings data
2157 2547 *
2158 - * @param $section_id
2159 - * @param $block_id
2160 - * @param $rawOptions
2548 + * @param string $section_id Section ID.
2549 + * @param string $block_id Block ID.
2550 + * @param array $rawOptions Raw Options.
2161 2551 *
2162 2552 * @return array
2163 2553 */
2164 - 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
2165 2555 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2166 2556 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2167 2557 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2168 -
2169 - $results = [
2558 + $results = [
2170 2559 'status' => true,
2171 2560 'message' => '',
2172 2561 ];
2173 2562 if ( ! $section_id || ! $block_id ) {
@@ -2183,15 +2572,15 @@
2183 2572
2184 2573 return $results;
2185 2574 }
2186 2575
2187 - $options = DataModel::source()->get_option( $section_id, [] );
2576 + $options = DataModel::source()->get_option( $section_id, [], false );
2188 2577 $changed = false;
2189 2578 $fields = [];
2190 2579 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2191 2580 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2192 2581 }
2193 -
2582 + do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2194 2583 if ( empty( $fields ) ) {
2195 2584 if ( isset( $rawOptions['active'] ) ) {
2196 2585 $changed = true;
2197 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2197,21 +2586,49 @@
2197 2586 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2198 2587 }
2199 2588 } else {
2200 2589 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2201 - if ( $raw_option_key === 'active' ) {
2590 + if ( 'active' === $raw_option_key ) {
2202 2591 $changed = true;
2203 - $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
2204 2593 } else {
2205 2594 if ( isset( $fields[ $raw_option_key ] ) ) {
2206 2595 $field = $fields[ $raw_option_key ];
2207 - if ( $field['type'] === 'switch' ) {
2596 + if ( 'switch' === $field['type'] ) {
2208 2597 $value = 'on' === $raw_value ? 'on' : '';
2209 2598 } elseif ( 'repeaters' === $field['type'] ) {
2210 - $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 + }
2211 2629 } else {
2212 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2213 -
2630 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2214 2631 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2215 2632 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2216 2633 $value = array_map( $field['sanitize_fn'], $raw_value );
2217 2634 } else {
@@ -2220,16 +2637,22 @@
2220 2637 } else {
2221 2638 $value = [];
2222 2639 }
2223 2640 } else {
2224 - if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2225 - $value = $field['sanitize_fn']( $raw_value );
2641 + if ( ! empty( $field['sanitize_fn'] ) ) {
2642 + if ( is_callable( $field['sanitize_fn'] ) ) {
2643 + $value = $field['sanitize_fn']( $raw_value );
2644 + } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2645 + $value = $raw_value;
2646 + } else {
2647 + $value = $field['sanitize_fn']( $raw_value );
2648 + }
2226 2649 } else {
2227 2650 $value = sanitize_text_field( $raw_value );
2228 2651 }
2229 2652 }
2230 2653 }
2231 - $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
2232 2655 $changed = true;
2233 2656 }
2234 2657 }
2235 2658 }
@@ -2249,29 +2672,16 @@
2249 2672
2250 2673 return $results;
2251 2674 }
2252 2675
2253 - /***
2254 - * @param $meta_key
2255 - * @param $meta_value
2676 + /**
2677 + * Count products by taxonomies.
2256 2678 *
2257 - * @return mixed|void
2679 + * @param array $terms Terms.
2680 + * @param string $taxonomy Taxonomy.
2258 2681 *
2259 - *
2260 - * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2261 - * if( ! $meta_key || ! $meta_value ){
2262 - * return;
2263 - * }
2264 - * global $wpdb;
2265 - * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2266 - * $the_products = $wpdb->get_col( $prepare_guery );
2267 - * if( count( $the_products ) > 0 ){
2268 - * return $the_products[0];
2269 - * }
2270 - * return;
2271 - * }
2682 + * @return int|void
2272 2683 */
2273 -
2274 2684 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2275 2685 if ( empty( $terms ) || ! is_array( $terms ) ) {
2276 2686 return;
2277 2687 }
@@ -2282,8 +2692,10 @@
2282 2692 ];
2283 2693
2284 2694 if ( 'product_cat' === $taxonomy ) {
2285 2695 $args['product_category_id'] = $terms;
2696 + } elseif ( 'product_brand' === $taxonomy ) {
2697 + $args['product_brand_id'] = $terms;
2286 2698 } else {
2287 2699 $args['product_tag_id'] = $terms;
2288 2700 }
2289 2701
@@ -2292,8 +2704,61 @@
2292 2704 return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2293 2705 }
2294 2706
2295 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 + /**
2296 2761 * Check if product filters widget has ajax.
2297 2762 *
2298 2763 * @param string $page Page name.
2299 2764 *
@@ -2307,9 +2772,12 @@
2307 2772 if ( 'page' === get_post_type() ) {
2308 2773 return false;
2309 2774 }
2310 2775
2311 - $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2776 + $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2777 + if ( ! $id ) {
2778 + return false;
2779 + }
2312 2780 $cache_key = 'product_filters_has_ajax_' . $id;
2313 2781 if ( isset( self::$cache[ $cache_key ] ) ) {
2314 2782 return self::$cache[ $cache_key ];
2315 2783 }
@@ -2318,8 +2786,9 @@
2318 2786
2319 2787 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2320 2788 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2321 2789 }
2790 +
2322 2791 self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2323 2792 return ! empty( $ajax[0] );
2324 2793 }
2325 2794
@@ -2337,9 +2806,11 @@
2337 2806
2338 2807 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2339 2808 $elmap = ElementorDataMap::instance();
2340 2809 $ajax = [];
2341 -
2810 + if ( ! $id ) {
2811 + return false;
2812 + }
2342 2813 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2343 2814 $ajax[] = ! isset( $data['settings']['active_filter'] );
2344 2815
2345 2816 }
@@ -2358,33 +2829,88 @@
2358 2829 if ( ! is_admin() ) {
2359 2830 return [];
2360 2831 }
2361 2832
2362 - $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
2363 2834
2364 2835 if ( isset( self::$cache[ $cache_key ] ) ) {
2365 2836 return self::$cache[ $cache_key ];
2366 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'";
2367 2845
2368 - $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 + }
2369 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' );
2370 2895 if ( ! $results ) {
2371 2896 global $wpdb;
2372 2897 $sql = "SELECT t.term_id, t.name
2373 2898 FROM {$wpdb->terms} t
2374 2899 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2375 - WHERE tt.taxonomy = 'product_cat'";
2900 + WHERE tt.taxonomy = 'product_tag'";
2376 2901
2377 2902 if ( $search_query ) {
2378 2903 $sql .= ' AND t.name LIKE %s';
2379 - $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // Escaping and wildcard
2904 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2380 2905 } else {
2381 2906 $prepared_sql = $sql; // No need for placeholders.
2382 2907 }
2383 2908
2384 - $results = $wpdb->get_results( $prepared_sql );
2909 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2385 2910 // Cache the results in the object cache for future use.
2386 - 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 );
2387 2913 }
2388 2914
2389 2915 $cats = [];
2390 2916 if ( ! is_array( $results ) && ! count( $results ) ) {
@@ -2463,8 +2989,9 @@
2463 2989 /**
2464 2990 * Get Post Types.
2465 2991 *
2466 2992 * @param string $search_query Search query.
2993 + * @param array $args Arguments.
2467 2994 *
2468 2995 * @return array
2469 2996 */
2470 2997 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2487,11 +3014,12 @@
2487 3014 $query_results = get_posts( $args );
2488 3015 foreach ( $query_results as $post ) {
2489 3016 $posts[] = [
2490 3017 'value' => $post->ID,
2491 - 'label' => $post->post_title,
3018 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2492 3019 ];
2493 3020 }
3021 +
2494 3022 if ( $search_query ) {
2495 3023 if ( strpos( '-error 404', $search_query ) ) {
2496 3024 $posts[] = [
2497 3025 'value' => 'error',
@@ -2498,12 +3026,14 @@
2498 3026 'label' => __( '404 Error', 'shopbuilder' ),
2499 3027 ];
2500 3028 }
2501 3029 } else {
2502 - $posts[] = [
2503 - 'value' => 'error',
2504 - 'label' => __( '404 Error', 'shopbuilder' ),
2505 - ];
3030 + if ( 'page' === $args['post_type'] ) {
3031 + $posts[] = [
3032 + 'value' => 'error',
3033 + 'label' => __( '404 Error', 'shopbuilder' ),
3034 + ];
3035 + }
2506 3036 }
2507 3037 } else {
2508 3038 $posts = self::get_registered_post_types( $search_query );
2509 3039 }
@@ -2531,9 +3061,9 @@
2531 3061 public static function renderView( $viewName, $args = [], $return = false ) {
2532 3062 $viewName = str_replace( '.', '/', $viewName );
2533 3063
2534 3064 if ( ! empty( $args ) && is_array( $args ) ) {
2535 - extract( $args );
3065 + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
2536 3066 }
2537 3067
2538 3068 $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
2539 3069
@@ -2551,35 +3081,48 @@
2551 3081 } else {
2552 3082 include $view_file;
2553 3083 }
2554 3084 }
2555 - /**
2556 - * @param int $limit Best-selling Base on product Limit.
2557 - * @param string $return_type id|Object.
2558 - *
2559 - * @return int
2560 - */
2561 - public static function best_selling_products( $limit = 10, $return_type = 'id' ) {
2562 - $cache_key = 'best_selling_products' . $return_type . $limit;
2563 3085
3086 + /**
3087 + * Best selling product query.
3088 + *
3089 + * @param int $minimum_sale Minimum sale.
3090 + * @param int $limit Total limit.
3091 + *
3092 + * @return array|mixed
3093 + */
3094 + public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3095 + $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
2564 3096 // Check if the data is in the cache.
3097 + if ( isset( self::$cache[ $cache_key ] ) ) {
3098 + return self::$cache[ $cache_key ];
3099 + }
2565 3100 $cached_data = get_transient( $cache_key );
2566 3101 if ( $cached_data ) {
3102 + self::$cache[ $cache_key ] = $cached_data;
2567 3103 return $cached_data;
2568 3104 }
2569 -
2570 - $args = [
2571 - 'limit' => $limit,
2572 - 'orderby' => 'total_sales',
2573 - ];
2574 - if ( 'id' === strtolower( $return_type ) ) {
2575 - $args['return'] = 'ids';
2576 - }
2577 - $best_selling = wc_get_products( $args );
2578 -
3105 + global $wpdb;
3106 + $sql = $wpdb->prepare(
3107 + "
3108 + SELECT ID
3109 + FROM {$wpdb->posts} AS p
3110 + LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3111 + WHERE p.post_type = 'product'
3112 + AND p.post_status = 'publish'
3113 + AND pm.meta_key = 'total_sales'
3114 + AND pm.meta_value >= %d
3115 + ORDER BY pm.meta_value + 0 DESC
3116 + LIMIT %d
3117 + ",
3118 + $minimum_sale,
3119 + $limit
3120 + );
3121 + $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
2579 3122 // Cache the result for future use.
2580 3123 set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
2581 -
3124 + self::$cache[ $cache_key ] = $best_selling;
2582 3125 return $best_selling;
2583 3126 }
2584 3127
2585 3128 /**
@@ -2602,12 +3145,14 @@
2602 3145
2603 3146 if ( isset( self::$cache[ $cache_key ] ) ) {
2604 3147 return self::$cache[ $cache_key ];
2605 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' ) );
2606 3154
2607 - // Get the product publish date.
2608 - $publish_timestamp = strtotime( $product->get_date_created()->date_i18n( 'Y-m-d H:i:s' ) );
2609 -
2610 3155 // Calculate the difference in days.
2611 3156 $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
2612 3157
2613 3158 // Check if the product is considered new.
@@ -2630,6 +3175,934 @@
2630 3175 public static function is_guest_feature_disabled( $option, $default ) {
2631 3176 $disabled = self::get_option( 'general', 'guest_user', $option, $default );
2632 3177
2633 3178 return ! is_user_logged_in() && $disabled;
3179 + }
3180 +
3181 + /**
3182 + * Checks if a feature is disabled for guest users based on the provided option.
3183 + *
3184 + * @return void
3185 + */
3186 + public static function woocommerce_output_all_notices() {
3187 + if ( function_exists( 'wc_print_notices' ) ) :
3188 + echo '<div class="rtsb-notice">';
3189 + woocommerce_output_all_notices();
3190 + echo '</div>';
3191 + endif;
3192 + }
3193 +
3194 + /**
3195 + * @param object $product Product.
3196 + * @return bool
3197 + */
3198 + public static function is_visible_qty_input( $product = null ) {
3199 + $is_visible_qty = true;
3200 + if ( $product instanceof \WC_Product ) {
3201 + if ( $product->is_sold_individually() ) {
3202 + $is_visible_qty = false;
3203 + } elseif ( $product->managing_stock() ) {
3204 + if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3205 + $is_visible_qty = true;
3206 + } elseif ( $product->get_stock_quantity() < 2 ) {
3207 + $is_visible_qty = false;
3208 + }
3209 + }
3210 + }
3211 + return $is_visible_qty;
3212 + }
3213 +
3214 + /**
3215 + * @param int $object_id Object id.
3216 + * @param string $element_type element type.
3217 + * @param string|null $current_lang null for current language, default for default languages.
3218 + * @return false|mixed|null
3219 + */
3220 + public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3221 + if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3222 + return $object_id;
3223 + }
3224 + if ( ! $object_id || ! $element_type ) {
3225 + return $object_id;
3226 + }
3227 + if ( 'default' === $current_lang ) {
3228 + $lang = apply_filters( 'wpml_default_language', null );
3229 + } else {
3230 + $lang = null;
3231 + }
3232 + return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3233 + }
3234 +
3235 + /**
3236 + * @return string
3237 + */
3238 + public static function wpml_current_language() {
3239 + $current = apply_filters( 'wpml_current_language', null );
3240 + $default = apply_filters( 'wpml_default_language', null );
3241 + return $default !== $current ? $current : null;
3242 + }
3243 +
3244 + /**
3245 + * Custom icons.
3246 + *
3247 + * @return string[]
3248 + */
3249 + public static function get_custom_icon_names() {
3250 + return [
3251 + 'heart-empty',
3252 + 'heart',
3253 + 'eye',
3254 + 'exchange',
3255 + 'plus',
3256 + 'minus',
3257 + 'avatar',
3258 + 'pay',
3259 + 'share',
3260 + 'clock',
3261 + 'check-alt',
3262 + 'check',
3263 + 'delete',
3264 + 'marker',
3265 + 'list',
3266 + 'list-2',
3267 + 'power',
3268 + 'cart',
3269 + 'cart-2',
3270 + 'cart-3',
3271 + 'downloads',
3272 + 'zoom',
3273 + 'user-edit',
3274 + 'grid',
3275 + 'filter',
3276 + 'billing',
3277 + 'login',
3278 + 'payment',
3279 + 'search',
3280 + 'edit',
3281 + 'coupon',
3282 + 'arrows-cw',
3283 + 'trash-empty',
3284 + ];
3285 + }
3286 +
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 + /**
3348 + * Flushes rewrite rules.
3349 + *
3350 + * @return void
3351 + */
3352 + public static function maybe_flush_rewrite_rules() {
3353 + add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3354 + add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3355 + }
3356 +
3357 + /**
3358 + * Flushes rewrite rules if needed.
3359 + *
3360 + * @return void
3361 + */
3362 + public static function flush_rewrite_rules() {
3363 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3364 + flush_rewrite_rules();
3365 + }
3366 +
3367 + if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3368 + flush_rewrite_rules();
3369 + delete_option( 'rtsb_permalinks_flushed' );
3370 + }
3371 + }
3372 +
3373 + /**
3374 + * Flushes rewrite rules if needed for second time.
3375 + *
3376 + * @return void
3377 + */
3378 + public static function flush_rewrite_rules_once_more() {
3379 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3380 + flush_rewrite_rules();
3381 + update_option( 'rtsb_permalinks_flushed', 'yes' );
3382 + delete_option( 'rtsb_permalinks_need_flush' );
3383 + }
3384 + }
3385 +
3386 + /**
3387 + * Social Share Platforms.
3388 + *
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
3397 + */
3398 + public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3399 + $modules = DataModel::source()->get_option( $section_id, [], false );
3400 + if ( empty( $modules[ $block_id ] ) ) {
3401 + return;
3402 + }
3403 + $options = $modules[ $block_id ];
3404 + if ( empty( $options[ $option_key ] ) ) {
3405 + return;
3406 + }
3407 + $repeater_options = json_decode( $options[ $option_key ], true );
3408 + // Check if options are not empty and are in array format.
3409 + if ( ! is_array( $repeater_options ) ) {
3410 + return;
3411 + }
3412 + // Use array_column to get an array of values from $compare_key.
3413 + $column_values = array_column( $repeater_options, $compare_key );
3414 + // Use array_search to find the index of the $compare_value.
3415 + $index = array_search( $compare_value, $column_values, true );
3416 + if ( false === $index ) {
3417 + return;
3418 + }
3419 + $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3420 + $options[ $option_key ] = wp_json_encode( $repeater_options );
3421 + $modules[ $block_id ] = $options;
3422 + DataModel::source()->set_option( $section_id, $modules );
3423 + }
3424 +
3425 +
3426 + /**
3427 + * @param array $ids products id.
3428 + * @return array
3429 + */
3430 + public static function get_available_products_by_ids( $ids = [] ) {
3431 + $ids = array_filter(
3432 + $ids,
3433 + function ( $id ) {
3434 + return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3435 + }
3436 + );
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 );
2634 4107 }
2635 4108 }