PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.1
ShopBuilder – WooCommerce Builder For Elementor v3.2.1
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 +1872 -175 2.0.3 → 3.2.1 View file →
@@ -11,14 +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;
19 +use WC_Product;
20 +use Elementor\Plugin;
21 +use WC_Product_Query;
15 22 use Elementor\Icons_Manager;
16 23 use RadiusTheme\SB\Models\ReSizer;
24 +use RadiusTheme\SB\Models\Settings;
17 25 use RadiusTheme\SB\Models\DataModel;
18 26 use RadiusTheme\SB\Models\ModuleList;
19 -use RadiusTheme\SB\Models\Settings;
20 -use WC_Product;
27 +use RadiusTheme\SB\Models\GeneralList;
28 +use RadiusTheme\SB\Models\ElementList;
29 +use RadiusTheme\SB\Controllers\AssetRegistry;
21 30
22 31 /**
23 32 * Fns class
24 33 */
@@ -24,8 +33,27 @@
24 33 */
25 34 class Fns {
26 35
27 36 /**
37 + * @var array
38 + */
39 + private static $cache = [];
40 + /**
41 + * Check if the stored license is valid.
42 + *
43 + * @return bool
44 + */
45 + public static function has_valid_license() {
46 + static $cached_result = null;
47 + // Return cached result if already calculated in this request.
48 + if ( null !== $cached_result ) {
49 + return $cached_result;
50 + }
51 + $license_status = rtsb()->has_pro() ? rtsbpro()->get_license( 'license_status' ) : '';
52 + return 'valid' === $license_status;
53 + }
54 +
55 + /**
28 56 * Verify nonce.
29 57 *
30 58 * @return bool
31 59 */
@@ -39,12 +67,118 @@
39 67 return false;
40 68 }
41 69
42 70 /**
43 - * @param $plugin_slug
71 + * Get nonce.
44 72 *
73 + * @return string|null
74 + */
75 + public static function enable_loader() {
76 + return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
77 + }
78 + /**
79 + * Get nonce.
80 + *
81 + * @return string|null
82 + */
83 + public static function content_invisible() {
84 + $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
85 + if ( $wp_rocket_compatibility ) {
86 + return true;
87 + }
88 + if ( defined( 'WP_ROCKET_VERSION' ) ) {
89 + return false;
90 + }
91 + return true;
92 + }
93 + /**
94 + * Get nonce.
95 + *
96 + * @return string|null
97 + */
98 + public static function get_nonce() {
99 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
100 + return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
101 + }
102 +
103 + /**
104 + * Set Session
105 + *
106 + * @param string $name Name.
107 + * @param mixed $value Value.
108 + */
109 + public static function setSession( $name, $value ) {
110 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
111 + session_start();
112 + $_SESSION[ $name ] = $value;
113 + } else {
114 + $_SESSION[ $name ] = $value;
115 + }
116 + }
117 +
118 + /**
119 + * Get Cookie or Session
120 + *
121 + * @param string $name Name.
122 + *
45 123 * @return bool
46 124 */
125 + public static function getSession( $name ) {
126 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
127 + session_start();
128 + }
129 + return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
130 + }
131 + /**
132 + * Remove Session Variable
133 + *
134 + * @param string $name The name of the session variable to remove.
135 + */
136 + public static function removeSession( $name ) {
137 + if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
138 + session_start();
139 + unset( $_SESSION[ $name ] );
140 + } else {
141 + unset( $_SESSION[ $name ] );
142 + }
143 + }
144 + /**
145 + * Gets the current timestamp in WordPress timezone.
146 + *
147 + * @return int Current timestamp.
148 + */
149 + public static function currentTimestampUTC() {
150 + $wp_timezone = wp_timezone();
151 + $now = new DateTime( 'now', $wp_timezone ); // Current time in WP timezone.
152 + return $now->getTimestamp();
153 + }
154 + /**
155 + * Action.
156 + *
157 + * @param string $action action.
158 + * @return void
159 + */
160 + public static function add_to_scheduled_hook_list( $action ) {
161 + if ( empty( $action ) ) {
162 + return;
163 + }
164 + $schedule = get_option( 'rtsb_cron_schedule_free', [] );
165 + $schedule[] = $action;
166 + update_option( 'rtsb_cron_schedule_free', array_unique( $schedule ) );
167 + }
168 + /**
169 + * @return DB
170 + */
171 + public static function DB() {
172 + return new DB( 'wpdb' );
173 + }
174 + /**
175 + * Check if a plugin is installed.
176 + *
177 + * @param string $plugin_slug Plugin slug.
178 + *
179 + * @return bool
180 + */
47 181 public static function check_plugin_installed( $plugin_slug ): bool {
48 182 $installed_plugins = get_plugins();
49 183
50 184 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
@@ -50,10 +184,12 @@
50 184 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
51 185 }
52 186
53 187 /**
54 - * @param $plugin_slug
188 + * Check if a plugin is active.
55 189 *
190 + * @param string $plugin_slug Plugin slug.
191 + *
56 192 * @return bool
57 193 */
58 194 public static function check_plugin_active( $plugin_slug ): bool {
59 195 if ( is_plugin_active( $plugin_slug ) ) {
@@ -61,21 +197,71 @@
61 197 }
62 198
63 199 return false;
64 200 }
201 + /**
202 + * Get all user roles.
203 + *
204 + * @return array
205 + */
206 + public static function get_current_user_roles() {
207 + $current_user = wp_get_current_user();
208 + return $current_user->roles;
209 + }
210 + /**
211 + * Get all user roles.
212 + *
213 + * @return array|false|mixed
214 + */
215 + public static function get_all_user_roles() {
216 + $cache_key = 'rtsb_get_user_roles';
217 + $roles = wp_cache_get( $cache_key, 'shopbuilder' );
65 218
219 + if ( empty( $roles ) ) {
220 + if ( ! function_exists( 'get_editable_roles' ) ) {
221 + require_once ABSPATH . 'wp-admin/includes/user.php';
222 + }
223 +
224 + $user_roles = \get_editable_roles();
225 + $roles = [];
226 +
227 + foreach ( $user_roles as $key => $role ) {
228 + if ( empty( $role['capabilities'] ) ) {
229 + continue;
230 + }
231 + $roles[ $key ] = $role['name'];
232 + }
233 + }
234 + wp_cache_set( $cache_key, $roles, 'shopbuilder' );
235 + Cache::set_data_cache_key( $cache_key );
236 + return $roles;
237 + }
238 +
66 239 /**
67 - * @param $data
240 + * Stripslashes value.
68 241 *
242 + * @param mixed $data Data.
243 + *
69 244 * @return mixed|string
70 245 */
71 246 public static function stripslashes_value( $data ) {
72 - if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
73 - return stripslashes( $data );
74 - } elseif ( is_array( $data ) ) {
247 + if ( is_array( $data ) ) {
75 248 foreach ( $data as $key => $value ) {
76 249 $data[ $key ] = self::stripslashes_value( $value );
77 250 }
251 + } elseif ( is_string( $data ) ) {
252 + $trimmed = trim( $data );
253 + // Try to decode JSON.
254 + $json = json_decode( $trimmed, true );
255 + if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
256 + // Recursively stripslashes on decoded array.
257 + $json = self::stripslashes_value( $json );
258 + return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
259 + }
260 + // Not valid JSON, just stripslashes if needed.
261 + if ( strpos( $data, '\\' ) !== false ) {
262 + return stripslashes( $data );
263 + }
78 264 }
79 265
80 266 return $data;
81 267 }
@@ -80,10 +266,12 @@
80 266 return $data;
81 267 }
82 268
83 269 /**
84 - * @param $string
270 + * Multiselect settings field value
85 271 *
272 + * @param string $string String.
273 + *
86 274 * @return array
87 275 */
88 276 public static function multiselect_settings_field_value( $string ) {
89 277
@@ -103,8 +291,10 @@
103 291 return $values;
104 292 }
105 293
106 294 /**
295 + * Check if is block theme
296 + *
107 297 * @return bool
108 298 */
109 299 public static function check_is_block_theme(): bool {
110 300 global $wp_version;
@@ -112,16 +302,19 @@
112 302 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
113 303 }
114 304
115 305 /**
306 + * Locate template
307 + *
116 308 * @param string $template_name Template name.
117 309 * @param string $template_path Template path. (default: '').
118 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
310 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
119 311 *
120 312 * @return mixed|void
121 313 */
122 314 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
123 - $template_name = $template_name . '.php';
315 + $template_name = self::sanitize_file_name( $template_name ) . '.php';
316 +
124 317 if ( ! $template_path ) {
125 318 $template_path = rtsb()->get_template_path();
126 319 }
127 320 if ( ! $plugin_path ) {
@@ -143,12 +336,12 @@
143 336 )
144 337 );
145 338
146 339 if ( ! $located ) {
147 - if ( file_exists( $plugin_path ) ) {
340 + if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
341 + return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
342 + } elseif ( file_exists( $plugin_path ) ) {
148 343 return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
149 - } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
150 - return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
151 344 }
152 345 }
153 346
154 347 /**
@@ -164,8 +357,19 @@
164 357 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
165 358 }
166 359
167 360 /**
361 + * Remove any character that is not alphanumeric, /, _, or -.
362 + *
363 + * @param string $name Name to sanitize.
364 + *
365 + * @return array|string|string[]|null
366 + */
367 + private static function sanitize_file_name( $name ) {
368 + return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
369 + }
370 +
371 + /**
168 372 * Template Content
169 373 *
170 374 * @param string $template_name Template name.
171 375 * @param array $args Arguments. (default: array).
@@ -174,11 +378,20 @@
174 378 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
175 379 *
176 380 * @return false|string|void
177 381 */
178 - public static function load_template( $template_name, array $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
179 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
180 -
382 + public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
383 + $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
384 + $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
385 + if ( ! $located ) {
386 + $located = self::locate_template( $template_name, $template_path, $plugin_path );
387 + // Don't cache the absolute path so that it can be shared between web servers with different paths.
388 + $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
389 + Cache::set_template_cache( $cache_key, $cache_path );
390 + } else {
391 + // Make sure that the absolute path to the template is resolved.
392 + $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
393 + }
181 394 if ( ! file_exists( $located ) ) {
182 395 // translators: %s template.
183 396 self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
184 397
@@ -186,9 +399,9 @@
186 399 }
187 400
188 401 if ( ! empty( $args ) && is_array( $args ) ) {
189 402 $atts = $args;
190 - extract( $args ); // @codingStandardsIgnoreLine
403 + extract( $args ); // @codingStandardsIgnoreLine
191 404 }
192 405
193 406 // Allow 3rd party plugin filter template file from their plugin.
194 407 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -198,8 +411,9 @@
198 411 }
199 412
200 413 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
201 414 include $located;
415 +
202 416 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
203 417
204 418 if ( $return ) {
205 419 return ob_get_clean();
@@ -302,9 +516,9 @@
302 516 // Return the Builder Template id.
303 517
304 518 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
305 519 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
306 - if ( $product_id ) {
520 + if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
307 521 return $product_id;
308 522 }
309 523 }
310 524
@@ -309,15 +523,16 @@
309 523 }
310 524
311 525 global $wpdb;
312 526 $cache_key = 'rtsb_prepared_product_id';
313 - $_post_id = wp_cache_get( $cache_key );
314 -
527 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
315 528 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
529 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
316 530 $_post_id = $wpdb->get_var(
317 - $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status = %s", 'product', 'publish' )
531 + $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
318 532 );
319 - wp_cache_set( $cache_key, $_post_id, '', 12 * HOUR_IN_SECONDS );
533 + wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
534 + Cache::set_data_cache_key( $cache_key );
320 535 }
321 536
322 537 return $_post_id;
323 538 }
@@ -322,19 +537,26 @@
322 537 return $_post_id;
323 538 }
324 539
325 540 /**
326 - * Get the product function.
541 + * Get the product function. Only used in single page widgets.
327 542 *
328 543 * @return object
329 544 */
330 545 public static function get_product() {
331 - global $post, $product;
546 + global $product;
547 +
332 548 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
333 549 return $product;
334 550 }
335 -
336 - return wc_get_product( self::get_prepared_product_id() );
551 + $cache_key = 'prepared_product_for_preview';
552 + if ( isset( self::$cache[ $cache_key ] ) ) {
553 + return self::$cache[ $cache_key ];
554 + }
555 + $product = wc_get_product( self::get_prepared_product_id() );
556 + self::$cache[ $cache_key ] = $product;
557 + do_action( 'rtsb_before_product_template_render' );
558 + return $product;
337 559 }
338 560
339 561 /**
340 562 * Error function
@@ -345,10 +567,11 @@
345 567 *
346 568 * @return void
347 569 */
348 570 public static function doing_it_wrong( $function, $message, $version ) {
571 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
349 572 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
350 - _doing_it_wrong( $function, $message, $version );
573 + _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
351 574 }
352 575
353 576 /**
354 577 * @return array
@@ -364,8 +587,15 @@
364 587
365 588 return $pages;
366 589 }
367 590
591 + /**
592 + * Get section items
593 + *
594 + * @param string $section_id Section ID.
595 + *
596 + * @return array
597 + */
368 598 public static function get_section_items( $section_id ) {
369 599 if ( ! $section_id ) {
370 600 return [];
371 601 }
@@ -372,8 +602,16 @@
372 602
373 603 return DataModel::source()->get_option( $section_id, [] );
374 604 }
375 605
606 + /**
607 + * Get options items
608 + *
609 + * @param string $section_id Section ID.
610 + * @param string $item_id Item ID.
611 + *
612 + * @return array
613 + */
376 614 public static function get_options( $section_id, $item_id ) {
377 615 if ( ! $section_id || ! $item_id ) {
378 616 return [];
379 617 }
@@ -384,11 +622,11 @@
384 622
385 623 /**
386 624 * Get options value with default value if options doesn't exist.
387 625 *
388 - * @param $group
389 - * @param $option_key
390 - * @param $default_value
626 + * @param array $group Group.
627 + * @param string $option_key Option key.
628 + * @param string $default_value Default value.
391 629 *
392 630 * @return mixed|string
393 631 */
394 632 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -400,32 +638,34 @@
400 638 return $group[ $option_key ];
401 639 }
402 640
403 641 /**
404 - * @param string $section_id
405 - * @param string $item_id
406 - * @param string $option_id
407 - * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value
408 - * @param null $type checkbox, multi_checkbox, number
642 + * Get option.
409 643 *
644 + * @param string $section_id Section ID.
645 + * @param string $item_id Item ID.
646 + * @param string $option_id Option ID.
647 + * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
648 + * @param null $type checkbox, multi_checkbox, number.
649 + *
410 650 * @return bool|int|mixed|null
411 651 */
412 652 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
413 653 $options = self::get_options( $section_id, $item_id );
414 654
415 - if ( $type === 'checkbox' ) {
655 + if ( 'checkbox' === $type ) {
416 656 if ( isset( $options[ $option_id ] ) ) {
417 - return $options[ $option_id ] === 'on';
657 + return 'on' === $options[ $option_id ];
418 658 }
419 659
420 660 return $default;
421 - } elseif ( $type === 'multi_checkbox' ) {
422 - return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] );
423 - } elseif ( $type === 'number' ) {
661 + } elseif ( 'multi_checkbox' === $type ) {
662 + return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
663 + } elseif ( 'number' === $type ) {
424 664 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
425 665 }
426 666
427 - return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
667 + return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
428 668 }
429 669
430 670
431 671 /**
@@ -479,13 +719,14 @@
479 719 }
480 720
481 721 if ( strlen( $page_content ) > 0 ) {
482 722 // Search for an existing page with the specified page content (typically a shortcode).
483 - $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
723 + $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
724 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
484 725 $valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_content LIKE %s LIMIT 1;", "%{$shortcode}%" ) );
485 726 } else {
486 727 // Search for an existing page with the specified page slug.
487 - $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 ) );
728 + $valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_name = %s LIMIT 1;", $slug ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
488 729 }
489 730
490 731 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
491 732
@@ -507,12 +748,12 @@
507 748
508 749 // Search for a matching valid trashed page.
509 750 if ( strlen( $page_content ) > 0 ) {
510 751 // Search for an existing page with the specified page content (typically a shortcode).
511 - $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}%" ) );
752 + $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
512 753 } else {
513 754 // Search for an existing page with the specified page slug.
514 - $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 ) );
755 + $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
515 756 }
516 757
517 758 if ( $trashed_page_found ) {
518 759 $page_id = $trashed_page_found;
@@ -551,8 +792,13 @@
551 792
552 793 return $page_id;
553 794 }
554 795
796 + /**
797 + * Get allowed HTML tags.
798 + *
799 + * @return array
800 + */
555 801 public static function get_kses_array() {
556 802 return [
557 803 'a' => [
558 804 'class' => [],
@@ -651,13 +897,14 @@
651 897 ];
652 898 }
653 899
654 900
655 - /*
901 + /**
656 902 * Escape output of wishlist icon
657 903 *
658 904 * @param string $data Data to escape.
659 - * @return string Escaped data
905 + *
906 + * @return void
660 907 */
661 908 public static function print_icon( $data ) {
662 909 /**
663 910 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -741,9 +988,9 @@
741 988 * @return mixed
742 989 */
743 990 public static function allowedHtml( $level = 'basic' ) {
744 991 $allowed_html = [];
745 - // TODO:: Need Optimize.
992 +
746 993 switch ( $level ) {
747 994 case 'basic':
748 995 $allowed_html = [
749 996 'b' => [
@@ -883,8 +1130,51 @@
883 1130 return $allowed_html;
884 1131 }
885 1132
886 1133 /**
1134 + * Safe get a validated HTML tag.
1135 + *
1136 + * @param string $tag HTML tag.
1137 + *
1138 + * @return string
1139 + */
1140 + public static function get_validated_html_tag( $tag ) {
1141 + $allowed_html_wrapper_tags = [
1142 + 'a',
1143 + 'article',
1144 + 'aside',
1145 + 'button',
1146 + 'div',
1147 + 'footer',
1148 + 'h1',
1149 + 'h2',
1150 + 'h3',
1151 + 'h4',
1152 + 'h5',
1153 + 'h6',
1154 + 'header',
1155 + 'main',
1156 + 'nav',
1157 + 'p',
1158 + 'section',
1159 + 'span',
1160 + ];
1161 +
1162 + return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1163 + }
1164 +
1165 + /**
1166 + * Safe print a validated HTML tag.
1167 + *
1168 + * @param string $tag HTML tag.
1169 + *
1170 + * @return void
1171 + */
1172 + public static function print_validated_html_tag( $tag ) {
1173 + self::print_html( self::get_validated_html_tag( $tag ) );
1174 + }
1175 +
1176 + /**
887 1177 * Insert Some array element
888 1178 *
889 1179 * @param [type] $key The elements will insert nearby this key.
890 1180 * @param [type] $main_array Original array.
@@ -947,11 +1237,17 @@
947 1237
948 1238 /**
949 1239 * Free Layouts
950 1240 *
1241 + * @param string $layout Layout.
1242 + *
951 1243 * @return array
952 1244 */
953 - public static function free_layouts() {
1245 + public static function free_layouts( $layout = '' ) {
1246 + if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1247 + return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1248 + }
1249 +
954 1250 $layouts = [
955 1251 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
956 1252 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
957 1253 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -979,10 +1275,19 @@
979 1275
980 1276 if ( empty( $taxonomy ) ) {
981 1277 return $terms;
982 1278 }
1279 + $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1280 + if ( isset( self::$cache[ $cache_key ] ) ) {
1281 + return self::$cache[ $cache_key ];
1282 + }
983 1283
984 - $termList = get_terms( [ $taxonomy ], [ 'hide_empty' => 0 ] );
1284 + $termList = get_terms(
1285 + [
1286 + 'taxonomy' => $taxonomy,
1287 + 'hide_empty' => false,
1288 + ]
1289 + );
985 1290
986 1291 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
987 1292 if ( $placeholder ) {
988 1293 $terms = [
@@ -993,11 +1298,28 @@
993 1298 foreach ( $termList as $term ) {
994 1299 $terms[ $term->term_id ] = esc_html( $term->name );
995 1300 }
996 1301 }
997 -
1302 + self::$cache[ $cache_key ] = $terms;
998 1303 return $terms;
999 1304 }
1305 + /**
1306 + * Get all product attribute taxonomy by id
1307 + *
1308 + * @param array $term_ids Term id.
1309 + *
1310 + * @return array
1311 + */
1312 + public static function tax_filter_attr_taxonomy( $term_ids ) {
1313 + $taxonomies = [];
1314 + foreach ( $term_ids as $id ) {
1315 + $term = get_term( $id );
1316 + if ( ! is_wp_error( $term ) && $term ) {
1317 + $taxonomies[] = $term->taxonomy;
1318 + }
1319 + }
1320 + return array_unique( $taxonomies );
1321 + }
1000 1322
1001 1323 /**
1002 1324 * Get all terms by attributes
1003 1325 *
@@ -1011,9 +1333,9 @@
1011 1333
1012 1334 if ( $attributes ) {
1013 1335 foreach ( $attributes as $tax ) {
1014 1336 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1015 - $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ), 'orderby=name&hide_empty=0' );
1337 + $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1016 1338 }
1017 1339 }
1018 1340 }
1019 1341
@@ -1105,9 +1427,12 @@
1105 1427 *
1106 1428 * @return int|array
1107 1429 */
1108 1430 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1109 - // TODO:: Return Slug always true for all settings.
1431 + if ( ! is_admin() ) {
1432 + return [];
1433 + }
1434 +
1110 1435 $term_list = [];
1111 1436 $args = [
1112 1437 'taxonomy' => $taxonomy,
1113 1438 'hide_empty' => false,
@@ -1158,9 +1483,9 @@
1158 1483 }
1159 1484
1160 1485 $html .= '<div class="product-rating">';
1161 1486 $html .= wc_get_rating_html( $average_rating, $rating_count );
1162 - $html .= ! empty( $html ) ? '<span class="count">(' . $average_rating . ')</span>' : '';
1487 + $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1163 1488 $html .= '</div>';
1164 1489
1165 1490 self::print_html( $html, true );
1166 1491
@@ -1171,15 +1496,15 @@
1171 1496 return '';
1172 1497 }
1173 1498
1174 1499 $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1175 - $average = $args['show_average_rating'] ? '<span class="count">(' . $average_rating . ')</span>' : '';
1500 + $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1176 1501 $count = '';
1177 1502
1178 1503 if ( $args['show_rating_count'] ) {
1179 - $count .= '<div class="count">';
1504 + $count .= '<div class="rtsb-count">';
1180 1505 $count .= sprintf(
1181 - /* translators: %s is the number of reviews */
1506 + /* translators: %s is the number of reviews */
1182 1507 _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1183 1508 $rating_count
1184 1509 );
1185 1510 $count .= '</div>';
@@ -1205,8 +1530,28 @@
1205 1530 self::print_html( $html, true );
1206 1531 }
1207 1532
1208 1533 /**
1534 + * Get product simple rating.
1535 + *
1536 + * @return void
1537 + */
1538 + public static function get_product_simple_rating_html() {
1539 + global $product;
1540 + $html = null;
1541 + $rating_count = $product->get_rating_count();
1542 + $average_rating = $product->get_average_rating();
1543 + $html .= '<div class="product-rating">';
1544 + $html .= wc_get_rating_html( $average_rating, $rating_count );
1545 + $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1546 + $html .= '</div>';
1547 +
1548 + self::print_html( $html, true );
1549 + }
1550 +
1551 +
1552 +
1553 + /**
1209 1554 * Text truncation.
1210 1555 *
1211 1556 * @param string $text_to_truncate Text.
1212 1557 * @param int $limit Limit.
@@ -1248,8 +1593,14 @@
1248 1593 *
1249 1594 * @return void
1250 1595 */
1251 1596 public static function get_badge_html( $text, $class = 'fill' ) {
1597 + if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1598 + do_action( 'rtsb/modules/product_badges/frontend/display' );
1599 +
1600 + return;
1601 + }
1602 +
1252 1603 if ( empty( $text ) ) {
1253 1604 return;
1254 1605 }
1255 1606 ob_start();
@@ -1256,10 +1607,9 @@
1256 1607 ?>
1257 1608
1258 1609 <ul class="rtsb-promotion-list">
1259 1610 <li class="rtsb-promotion-list-item">
1260 - <span
1261 - class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1611 + <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1262 1612 </li>
1263 1613 </ul>
1264 1614
1265 1615 <?php
@@ -1297,10 +1647,42 @@
1297 1647
1298 1648 <?php
1299 1649 self::print_html( ob_get_clean() );
1300 1650 }
1651 + /**
1652 + * Brands HTML
1653 + *
1654 + * @param int $id Product ID.
1655 + * @param string $class Custom class.
1656 + *
1657 + * @return void|string
1658 + */
1659 + public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1660 + $terms = get_the_terms( $id, 'product_brand' );
1661 + if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1662 + return '';
1663 + }
1664 + ob_start();
1665 + ?>
1301 1666
1667 + <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1668 + <?php
1669 + $brand_list = get_the_term_list(
1670 + $id,
1671 + 'product_brand',
1672 + '<li class="rtsb-brand-list-item">',
1673 + '</li><li class="rtsb-brand-list-item">',
1674 + '</li>'
1675 + );
1302 1676
1677 + self::print_html( $brand_list );
1678 + ?>
1679 + </ul>
1680 +
1681 + <?php
1682 + self::print_html( ob_get_clean() );
1683 + }
1684 +
1303 1685 /**
1304 1686 * Get Featured Image HTML.
1305 1687 *
1306 1688 * @param string $type Image type.
@@ -1310,12 +1692,13 @@
1310 1692 * @param array $custom_img_size Custom image size.
1311 1693 * @param bool $lazy Lazy load check.
1312 1694 * @param bool $hover Hover image.
1313 1695 * @param bool $gallery Gallery image.
1696 + * @param bool $custom_image_id Custom category image ID.
1314 1697 *
1315 1698 * @return string|null
1316 1699 */
1317 - public static function get_product_image_html( $type = 'product', $post_id = null, $f_img_size = 'medium', $default_img_id = null, $custom_img_size = [], $lazy = false, $hover = false, $gallery = false ) {
1700 + public static function get_product_image_html( $type = 'product', $post_id = null, $f_img_size = 'medium', $default_img_id = null, $custom_img_size = [], $lazy = false, $hover = false, $gallery = false, $custom_image_id = 0 ) {
1318 1701 $img_html = null;
1319 1702 $attachment_id = null;
1320 1703 $c_size = false;
1321 1704 $hover_class = '';
@@ -1339,9 +1722,9 @@
1339 1722 if ( 'product' === $type ) {
1340 1723 $a_id = get_post_thumbnail_id( $post_id );
1341 1724 $post_title = get_the_title( $post_id );
1342 1725 } elseif ( 'category' === $type ) {
1343 - $a_id = get_term_meta( $post_id, 'thumbnail_id', true );
1726 + $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1344 1727 $post_title = get_term( $post_id )->name;
1345 1728 } else {
1346 1729 $a_id = $default_img_id;
1347 1730 }
@@ -1402,9 +1785,9 @@
1402 1785
1403 1786 if ( ! $img_html ) {
1404 1787 $hwstring = image_hwstring( 160, 160 );
1405 1788 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1406 - $attr['class'] = 'default-img';
1789 + $attr['class'] = 'default-img ' . $hover_class;
1407 1790 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1408 1791 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1409 1792 $img_html = rtrim( "<img $hwstring" );
1410 1793
@@ -1491,8 +1874,20 @@
1491 1874 *
1492 1875 * @return string
1493 1876 */
1494 1877 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
1878 + if ( empty( $control['value'] ) ) {
1879 + return '';
1880 + }
1881 + if ( is_array( $control['value'] ) ) {
1882 + $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
1883 + } else {
1884 + $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
1885 + }
1886 + if ( isset( self::$cache[ $cache_key ] ) ) {
1887 + return self::$cache[ $cache_key ];
1888 + }
1889 +
1495 1890 $attributes = [
1496 1891 'aria-hidden' => 'true',
1497 1892 ];
1498 1893
@@ -1505,9 +1900,11 @@
1505 1900 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
1506 1901 Icons_Manager::render_icon( $control, $attributes );
1507 1902 }
1508 1903
1509 - return ob_get_clean();
1904 + $icons = ob_get_clean();
1905 + self::$cache[ $cache_key ] = $icons;
1906 + return $icons;
1510 1907 }
1511 1908
1512 1909 /**
1513 1910 * Get product swatches.
@@ -1519,9 +1916,13 @@
1519 1916 public static function get_product_swatches( $type ) {
1520 1917 ?>
1521 1918 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
1522 1919 <?php
1523 - do_action( 'rtwpvs_show_archive_variation' );
1920 + if ( class_exists( 'Rtwpvsp' ) ) {
1921 + do_action( 'rtwpvs_show_archive_variation' );
1922 + } else {
1923 + do_action( 'rtsb/vs/showcase/variation' );
1924 + }
1524 1925 ?>
1525 1926 </div>
1526 1927 <?php
1527 1928 }
@@ -1545,38 +1946,63 @@
1545 1946 if ( ! $product->is_on_sale() ) {
1546 1947 return '';
1547 1948 }
1548 1949
1549 - $badge_text = '';
1550 - $percentage = null;
1551 - $max_percentage = '';
1950 + $badge_text = '';
1951 + $percentage = self::calculate_sale_percentage( $product );
1552 1952
1553 - if ( $product->is_type( 'simple' ) ) {
1554 - $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
1555 - } elseif ( $product->is_type( 'variable' ) ) {
1556 - $max_percentage = 0;
1953 + if ( $percentage > 0 ) {
1954 + $badge_text = '-' . round( $percentage ) . '%';
1955 + }
1557 1956
1558 - foreach ( $product->get_children() as $child_id ) {
1957 + if ( 'text' === $type ) {
1958 + $badge_text = $text;
1959 + }
1559 1960
1560 - $variation = wc_get_product( $child_id );
1961 + return $badge_text;
1962 + }
1561 1963
1562 - $price = $variation->get_regular_price();
1563 - $sale = $variation->get_sale_price();
1964 + /**
1965 + * Get sale badge
1966 + *
1967 + * @param object $product Product Object.
1968 + * @param string $type Badge type.
1969 + * @param string $text Badge text.
1970 + * @param string $out_of_stock_text Out of stock text.
1971 + *
1972 + * @return string
1973 + */
1974 + public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
1975 + $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
1976 + $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1564 1977
1565 - if ( 0 !== $price && ! empty( $sale ) ) {
1566 - $percentage = ( $price - $sale ) / $price * 100;
1567 - }
1978 + if ( $badges_visibility ) {
1979 + return '';
1980 + }
1568 1981
1569 - if ( $percentage > $max_percentage ) {
1570 - $max_percentage = $percentage;
1571 - }
1572 - }
1982 + if ( is_null( $product ) ) {
1983 + global $product;
1573 1984 }
1574 1985
1575 - if ( $max_percentage > 0 ) {
1576 - $badge_text = '-' . round( $max_percentage ) . '%';
1986 + if ( ! $product instanceof WC_Product ) {
1987 + return '';
1577 1988 }
1578 1989
1990 + if ( ! $product->is_in_stock() ) {
1991 + return $out_of_stock_text;
1992 + }
1993 +
1994 + if ( ! $product->is_on_sale() ) {
1995 + return '';
1996 + }
1997 +
1998 + $badge_text = '';
1999 + $percentage = self::calculate_sale_percentage( $product );
2000 +
2001 + if ( $percentage > 0 ) {
2002 + $badge_text = '-' . round( $percentage ) . '%';
2003 + }
2004 +
1579 2005 if ( 'text' === $type ) {
1580 2006 $badge_text = $text;
1581 2007 }
1582 2008
@@ -1583,8 +2009,48 @@
1583 2009 return $badge_text;
1584 2010 }
1585 2011
1586 2012 /**
2013 + * Calculate Sale Percentage
2014 + *
2015 + * @param object $product Product object.
2016 + *
2017 + * @return float|int|string
2018 + */
2019 + public static function calculate_sale_percentage( $product = null ) {
2020 + if ( is_null( $product ) ) {
2021 + global $product;
2022 + }
2023 + if ( ! $product instanceof WC_Product ) {
2024 + return '';
2025 + }
2026 + if ( ! $product->is_on_sale() ) {
2027 + return '';
2028 + }
2029 + $max_percentage = 0;
2030 + if ( $product->is_type( 'simple' ) ) {
2031 + $regular_price = (float) $product->get_regular_price();
2032 + $sale_price = (float) $product->get_sale_price();
2033 + if ( $regular_price > 0 && $sale_price > 0 ) {
2034 + $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2035 + }
2036 + } elseif ( $product->is_type( 'variable' ) ) {
2037 + $prices = $product->get_variation_prices( true );
2038 + if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
2039 + foreach ( $prices['regular_price'] as $key => $regular_price ) {
2040 + $sale_price = $prices['sale_price'][ $key ];
2041 +
2042 + if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
2043 + $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2044 + $max_percentage = max( $max_percentage, $percentage );
2045 + }
2046 + }
2047 + }
2048 + }
2049 + return $max_percentage > 0 ? round( $max_percentage ) : 0;
2050 + }
2051 +
2052 + /**
1587 2053 * Pagination
1588 2054 *
1589 2055 * @param string $pages Pages.
1590 2056 * @param integer $range Range.
@@ -1592,21 +2058,21 @@
1592 2058 *
1593 2059 * @return string
1594 2060 */
1595 2061 public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
1596 - $html = null;
1597 - $showitems = ( $range * 2 ) + 1;
2062 + $html = null;
2063 + $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
1598 2064
1599 2065 global $paged;
1600 2066
1601 2067 if ( is_front_page() ) {
1602 - $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
2068 + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1603 2069 } else {
1604 - $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
2070 + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1605 2071 }
1606 2072
1607 2073 if ( empty( $paged ) ) {
1608 - $paged = 1;
2074 + $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1609 2075 }
1610 2076
1611 2077 if ( '' === $pages ) {
1612 2078 global $wp_query;
@@ -1623,8 +2089,9 @@
1623 2089
1624 2090 if ( $ajax ) {
1625 2091 $ajaxClass = ' rtsb-pagination-ajax';
1626 2092 $dataAttr = "data-paged='1'";
2093 + $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
1627 2094 }
1628 2095
1629 2096 if ( 1 !== $pages ) {
1630 2097 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
@@ -1629,29 +2096,29 @@
1629 2096 if ( 1 !== $pages ) {
1630 2097 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
1631 2098 $html .= '<ul class="pagination-list">';
1632 2099
1633 - if ( $paged > 2 && $paged > $range + 1 && $showitems < $pages ) {
2100 + if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
1634 2101 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
1635 2102 }
1636 2103
1637 - if ( $paged > 1 && $showitems < $pages ) {
2104 + if ( $paged > 1 && $visible_range < $pages ) {
1638 2105 $p = $paged - 1;
1639 2106 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
1640 2107 }
1641 2108
1642 2109 for ( $i = 1; $i <= $pages; $i++ ) {
1643 - if ( 1 != $pages && ( ! ( $i >= $paged + $range + 1 || $i <= $paged - $range - 1 ) || $pages <= $showitems ) ) {
2110 + if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
1644 2111 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
1645 2112 }
1646 2113 }
1647 2114
1648 - if ( $paged < $pages && $showitems < $pages ) {
2115 + if ( $paged < $pages && $visible_range < $pages ) {
1649 2116 $p = $paged + 1;
1650 2117 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
1651 2118 }
1652 2119
1653 - if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $showitems < $pages ) {
2120 + if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
1654 2121 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
1655 2122 }
1656 2123
1657 2124 $html .= '</ul>';
@@ -1763,10 +2230,8 @@
1763 2230 } else {
1764 2231 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
1765 2232 }
1766 2233
1767 - $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
1768 -
1769 2234 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
1770 2235 $html .= $cart_html;
1771 2236 } else {
1772 2237 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -1771,9 +2236,11 @@
1771 2236 } else {
1772 2237 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
1773 2238 }
1774 2239
1775 - $html .= '</' . esc_attr( $wrapper ) . '>';
2240 + if ( ! empty( $html ) ) {
2241 + $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2242 + }
1776 2243
1777 2244 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
1778 2245 }
1779 2246
@@ -1874,9 +2341,9 @@
1874 2341 $link['social_network'] = 'Facebook';
1875 2342 $link['social_action'] = 'Share';
1876 2343 break;
1877 2344 case 'twitter':
1878 - $link['link'] = esc_url( 'https://twitter.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
2345 + $link['link'] = esc_url( 'https://x.com/intent/tweet?text=' . htmlspecialchars( rawurlencode( html_entity_decode( $link['title'], ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' ) . '&url=' . $link['url'] );
1879 2346 $link['icon'] = '<svg width="24" height="24" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" /></svg>';
1880 2347 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
1881 2348 $link['social_network'] = 'Twitter';
1882 2349 $link['social_action'] = 'Tweet';
@@ -1910,9 +2377,9 @@
1910 2377 $link['social_action'] = 'Share';
1911 2378 break;
1912 2379 case 'reddit':
1913 2380 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
1914 - $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5_logos</title><path d="M324,256a36,36,0,1,0,36,36A36,36,0,0,0,324,256Z"/><circle cx="188" cy="292" r="36" transform="translate(-97.43 94.17) rotate(-22.5)"/><path d="M496,253.77c0-31.19-25.14-56.56-56-56.56a55.72,55.72,0,0,0-35.61,12.86c-35-23.77-80.78-38.32-129.65-41.27l22-79L363.15,103c1.9,26.48,24,47.49,50.65,47.49,28,0,50.78-23,50.78-51.21S441,48,413,48c-19.53,0-36.31,11.19-44.85,28.77l-90-17.89L247.05,168.4l-4.63.13c-50.63,2.21-98.34,16.93-134.77,41.53A55.38,55.38,0,0,0,72,197.21c-30.89,0-56,25.37-56,56.56a56.43,56.43,0,0,0,28.11,49.06,98.65,98.65,0,0,0-.89,13.34c.11,39.74,22.49,77,63,105C146.36,448.77,199.51,464,256,464s109.76-15.23,149.83-42.89c40.53-28,62.85-65.27,62.85-105.06a109.32,109.32,0,0,0-.84-13.3A56.32,56.32,0,0,0,496,253.77ZM414,75a24,24,0,1,1-24,24A24,24,0,0,1,414,75ZM42.72,253.77a29.6,29.6,0,0,1,29.42-29.71,29,29,0,0,1,13.62,3.43c-15.5,14.41-26.93,30.41-34.07,47.68A30.23,30.23,0,0,1,42.72,253.77ZM390.82,399c-35.74,24.59-83.6,38.14-134.77,38.14S157,423.61,121.29,399c-33-22.79-51.24-52.26-51.24-83A78.5,78.5,0,0,1,75,288.72c5.68-15.74,16.16-30.48,31.15-43.79a155.17,155.17,0,0,1,14.76-11.53l.3-.21,0,0,.24-.17c35.72-24.52,83.52-38,134.61-38s98.9,13.51,134.62,38l.23.17.34.25A156.57,156.57,0,0,1,406,244.92c15,13.32,25.48,28.05,31.16,43.81a85.44,85.44,0,0,1,4.31,17.67,77.29,77.29,0,0,1,.6,9.65C442.06,346.77,423.86,376.24,390.82,399Zm69.6-123.92c-7.13-17.28-18.56-33.29-34.07-47.72A29.09,29.09,0,0,1,440,224a29.59,29.59,0,0,1,29.41,29.71A30.07,30.07,0,0,1,460.42,275.1Z"/><path d="M323.23,362.22c-.25.25-25.56,26.07-67.15,26.27-42-.2-66.28-25.23-67.31-26.27h0a4.14,4.14,0,0,0-5.83,0l-13.7,13.47a4.15,4.15,0,0,0,0,5.89h0c3.4,3.4,34.7,34.23,86.78,34.45,51.94-.22,83.38-31.05,86.78-34.45h0a4.16,4.16,0,0,0,0-5.9l-13.71-13.47a4.13,4.13,0,0,0-5.81,0Z"/></svg>';
2381 + $link['icon'] = '<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path d="M324,256a36,36,0,1,0,36,36A36,36,0,0,0,324,256Z"/><circle cx="188" cy="292" r="36" transform="translate(-97.43 94.17) rotate(-22.5)"/><path d="M496,253.77c0-31.19-25.14-56.56-56-56.56a55.72,55.72,0,0,0-35.61,12.86c-35-23.77-80.78-38.32-129.65-41.27l22-79L363.15,103c1.9,26.48,24,47.49,50.65,47.49,28,0,50.78-23,50.78-51.21S441,48,413,48c-19.53,0-36.31,11.19-44.85,28.77l-90-17.89L247.05,168.4l-4.63.13c-50.63,2.21-98.34,16.93-134.77,41.53A55.38,55.38,0,0,0,72,197.21c-30.89,0-56,25.37-56,56.56a56.43,56.43,0,0,0,28.11,49.06,98.65,98.65,0,0,0-.89,13.34c.11,39.74,22.49,77,63,105C146.36,448.77,199.51,464,256,464s109.76-15.23,149.83-42.89c40.53-28,62.85-65.27,62.85-105.06a109.32,109.32,0,0,0-.84-13.3A56.32,56.32,0,0,0,496,253.77ZM414,75a24,24,0,1,1-24,24A24,24,0,0,1,414,75ZM42.72,253.77a29.6,29.6,0,0,1,29.42-29.71,29,29,0,0,1,13.62,3.43c-15.5,14.41-26.93,30.41-34.07,47.68A30.23,30.23,0,0,1,42.72,253.77ZM390.82,399c-35.74,24.59-83.6,38.14-134.77,38.14S157,423.61,121.29,399c-33-22.79-51.24-52.26-51.24-83A78.5,78.5,0,0,1,75,288.72c5.68-15.74,16.16-30.48,31.15-43.79a155.17,155.17,0,0,1,14.76-11.53l.3-.21,0,0,.24-.17c35.72-24.52,83.52-38,134.61-38s98.9,13.51,134.62,38l.23.17.34.25A156.57,156.57,0,0,1,406,244.92c15,13.32,25.48,28.05,31.16,43.81a85.44,85.44,0,0,1,4.31,17.67,77.29,77.29,0,0,1,.6,9.65C442.06,346.77,423.86,376.24,390.82,399Zm69.6-123.92c-7.13-17.28-18.56-33.29-34.07-47.72A29.09,29.09,0,0,1,440,224a29.59,29.59,0,0,1,29.41,29.71A30.07,30.07,0,0,1,460.42,275.1Z"/><path d="M323.23,362.22c-.25.25-25.56,26.07-67.15,26.27-42-.2-66.28-25.23-67.31-26.27h0a4.14,4.14,0,0,0-5.83,0l-13.7,13.47a4.15,4.15,0,0,0,0,5.89h0c3.4,3.4,34.7,34.23,86.78,34.45,51.94-.22,83.38-31.05,86.78-34.45h0a4.16,4.16,0,0,0,0-5.9l-13.71-13.47a4.13,4.13,0,0,0-5.81,0Z"/></svg>';
1915 2382 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
1916 2383 $link['social_network'] = 'Reddit';
1917 2384 $link['social_action'] = 'Share';
1918 2385 break;
@@ -2000,33 +2467,49 @@
2000 2467
2001 2468 /**
2002 2469 * Social Share Platforms.
2003 2470 *
2004 - * @return mixed|null
2471 + * @param string $module Module.
2472 + *
2473 + * @return true|void
2005 2474 */
2006 2475 public static function is_module_active( $module ) {
2007 - $modulelist = ModuleList::instance()->get_data();
2476 + $modulelist = self::get_modules_list();
2477 +
2008 2478 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2009 2479 return true;
2010 2480 }
2011 2481 }
2012 2482
2483 + /**
2484 + * Elementor Widget Active.
2485 + *
2486 + * @param string $widget Elementor Widget.
2487 + *
2488 + * @return true|void
2489 + */
2490 + public static function is_elementor_widget_active( $widget ) {
2491 + $element_list = self::get_widgets_list();
2013 2492
2493 + if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2494 + return true;
2495 + }
2496 + }
2497 +
2014 2498 /***
2015 2499 * Save Settings data
2016 2500 *
2017 - * @param $section_id
2018 - * @param $block_id
2019 - * @param $rawOptions
2501 + * @param string $section_id Section ID.
2502 + * @param string $block_id Block ID.
2503 + * @param array $rawOptions Raw Options.
2020 2504 *
2021 2505 * @return array
2022 2506 */
2023 - public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2507 + public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
2024 2508 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2025 2509 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2026 2510 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2027 -
2028 - $results = [
2511 + $results = [
2029 2512 'status' => true,
2030 2513 'message' => '',
2031 2514 ];
2032 2515 if ( ! $section_id || ! $block_id ) {
@@ -2042,15 +2525,15 @@
2042 2525
2043 2526 return $results;
2044 2527 }
2045 2528
2046 - $options = DataModel::source()->get_option( $section_id, [] );
2529 + $options = DataModel::source()->get_option( $section_id, [], false );
2047 2530 $changed = false;
2048 2531 $fields = [];
2049 2532 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2050 2533 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2051 2534 }
2052 -
2535 + do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2053 2536 if ( empty( $fields ) ) {
2054 2537 if ( isset( $rawOptions['active'] ) ) {
2055 2538 $changed = true;
2056 2539 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2056,21 +2539,49 @@
2056 2539 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2057 2540 }
2058 2541 } else {
2059 2542 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2060 - if ( $raw_option_key === 'active' ) {
2543 + if ( 'active' === $raw_option_key ) {
2061 2544 $changed = true;
2062 - $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : '';
2545 + $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2063 2546 } else {
2064 2547 if ( isset( $fields[ $raw_option_key ] ) ) {
2065 2548 $field = $fields[ $raw_option_key ];
2066 - if ( $field['type'] === 'switch' ) {
2549 + if ( 'switch' === $field['type'] ) {
2067 2550 $value = 'on' === $raw_value ? 'on' : '';
2068 2551 } elseif ( 'repeaters' === $field['type'] ) {
2069 - $value = stripslashes_deep( $raw_value );
2552 + $the_value = [];
2553 + $rep_title = [];
2554 + if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2555 + foreach ( $raw_value as $key => $value ) {
2556 + if ( is_string( $value ) ) {
2557 + $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2558 + } else {
2559 + $the_value_decoded = $value;
2560 + }
2561 + $cr = $the_value_decoded->title ?? '';
2562 + if ( in_array( $cr, $rep_title, true ) ) {
2563 + continue;
2564 + }
2565 + $rep_title[] = $cr;
2566 + $the_value[] = $the_value_decoded;
2567 + }
2568 + } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2569 + $the_value = $raw_value;
2570 + }
2571 + $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2572 + } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2573 + $manual_field_value = [];
2574 + if ( is_array( $raw_value ) ) {
2575 + foreach ( $raw_value as $key => $value ) {
2576 + $manual_field_value[] = json_decode( stripslashes( $value ), true );
2577 + }
2578 + $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2579 + } elseif ( is_string( $raw_value ) ) {
2580 + $value = $raw_value;
2581 + }
2070 2582 } else {
2071 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2072 -
2583 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2073 2584 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2074 2585 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2075 2586 $value = array_map( $field['sanitize_fn'], $raw_value );
2076 2587 } else {
@@ -2079,16 +2590,22 @@
2079 2590 } else {
2080 2591 $value = [];
2081 2592 }
2082 2593 } else {
2083 - if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2084 - $value = $field['sanitize_fn']( $raw_value );
2594 + if ( ! empty( $field['sanitize_fn'] ) ) {
2595 + if ( is_callable( $field['sanitize_fn'] ) ) {
2596 + $value = $field['sanitize_fn']( $raw_value );
2597 + } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2598 + $value = $raw_value;
2599 + } else {
2600 + $value = $field['sanitize_fn']( $raw_value );
2601 + }
2085 2602 } else {
2086 2603 $value = sanitize_text_field( $raw_value );
2087 2604 }
2088 2605 }
2089 2606 }
2090 - $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null;
2607 + $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2091 2608 $changed = true;
2092 2609 }
2093 2610 }
2094 2611 }
@@ -2108,56 +2625,90 @@
2108 2625
2109 2626 return $results;
2110 2627 }
2111 2628
2112 - /***
2113 - * @param $meta_key
2114 - * @param $meta_value
2629 + /**
2630 + * Count products by taxonomies.
2115 2631 *
2116 - * @return mixed|void
2632 + * @param array $terms Terms.
2633 + * @param string $taxonomy Taxonomy.
2117 2634 *
2118 - *
2119 - * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2120 - * if( ! $meta_key || ! $meta_value ){
2121 - * return;
2122 - * }
2123 - * global $wpdb;
2124 - * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2125 - * $the_products = $wpdb->get_col( $prepare_guery );
2126 - * if( count( $the_products ) > 0 ){
2127 - * return $the_products[0];
2128 - * }
2129 - * return;
2130 - * }
2635 + * @return int|void
2131 2636 */
2132 -
2133 2637 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2134 2638 if ( empty( $terms ) || ! is_array( $terms ) ) {
2135 2639 return;
2136 2640 }
2137 2641
2138 - $category_ids = array_map(
2139 - function ( $name ) {
2140 - return sanitize_title( $name );
2141 - },
2142 - $terms
2143 - );
2642 + $args = [
2643 + 'limit' => -1,
2644 + 'return' => 'ids',
2645 + ];
2144 2646
2647 + if ( 'product_cat' === $taxonomy ) {
2648 + $args['product_category_id'] = $terms;
2649 + } elseif ( 'product_brand' === $taxonomy ) {
2650 + $args['product_brand_id'] = $terms;
2651 + } else {
2652 + $args['product_tag_id'] = $terms;
2653 + }
2654 +
2655 + $query = new WC_Product_Query( $args );
2656 +
2657 + return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2658 + }
2659 +
2660 + /**
2661 + * Count products by attribute terms.
2662 + *
2663 + * @param array $term_ids Term IDs.
2664 + * @param string $relation Relation.
2665 + *
2666 + * @return int
2667 + */
2668 + public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2669 + if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2670 + return 0;
2671 + }
2672 +
2673 + $terms_by_taxonomy = [];
2674 +
2675 + foreach ( $term_ids as $term_id ) {
2676 + $term = get_term( $term_id );
2677 + if ( ! is_wp_error( $term ) && $term ) {
2678 + if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2679 + $terms_by_taxonomy[ $term->taxonomy ] = [];
2680 + }
2681 + $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2682 + }
2683 + }
2684 +
2685 + if ( empty( $terms_by_taxonomy ) ) {
2686 + return 0;
2687 + }
2688 +
2145 2689 $args = [
2146 - 'post_type' => 'product',
2147 - 'posts_per_page' => - 1,
2148 - 'tax_query' => [
2149 - [
2150 - 'taxonomy' => $taxonomy,
2151 - 'field' => 'term_id',
2152 - 'terms' => $category_ids,
2153 - ],
2690 + 'status' => 'publish',
2691 + 'limit' => -1,
2692 + 'return' => 'ids',
2693 + 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2694 + 'relation' => strtoupper( $relation ),
2154 2695 ],
2155 2696 ];
2156 2697
2157 - $query = new \WP_Query( $args );
2698 + foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2699 + $args['tax_query'][] = [
2700 + 'taxonomy' => $taxonomy,
2701 + 'field' => 'term_id',
2702 + 'terms' => $terms,
2703 + 'operator' => 'IN',
2704 + ];
2705 + }
2158 2706
2159 - return $query->found_posts;
2707 + $query = new WC_Product_Query( $args );
2708 + $products = $query->get_products();
2709 +
2710 + return count( $products );
2160 2711 }
2161 2712
2162 2713 /**
2163 2714 * Check if product filters widget has ajax.
@@ -2170,9 +2721,20 @@
2170 2721 if ( empty( $page ) ) {
2171 2722 return false;
2172 2723 }
2173 2724
2174 - $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2725 + if ( 'page' === get_post_type() ) {
2726 + return false;
2727 + }
2728 +
2729 + $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2730 + if ( ! $id ) {
2731 + return false;
2732 + }
2733 + $cache_key = 'product_filters_has_ajax_' . $id;
2734 + if ( isset( self::$cache[ $cache_key ] ) ) {
2735 + return self::$cache[ $cache_key ];
2736 + }
2175 2737 $elmap = ElementorDataMap::instance();
2176 2738 $ajax = [];
2177 2739
2178 2740 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
@@ -2178,8 +2740,9 @@
2178 2740 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2179 2741 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2180 2742 }
2181 2743
2744 + self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2182 2745 return ! empty( $ajax[0] );
2183 2746 }
2184 2747
2185 2748 /**
@@ -2196,9 +2759,11 @@
2196 2759
2197 2760 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2198 2761 $elmap = ElementorDataMap::instance();
2199 2762 $ajax = [];
2200 -
2763 + if ( ! $id ) {
2764 + return false;
2765 + }
2201 2766 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2202 2767 $ajax[] = ! isset( $data['settings']['active_filter'] );
2203 2768
2204 2769 }
@@ -2213,24 +2778,94 @@
2213 2778 *
2214 2779 * @return array An array of product categories with 'value' and 'label' keys.
2215 2780 */
2216 2781 public static function products_category_query( $search_query = null ) {
2217 - global $wpdb;
2782 + if ( ! is_admin() ) {
2783 + return [];
2784 + }
2218 2785
2219 - $sql = "SELECT t.term_id, t.name
2786 + $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2787 +
2788 + if ( isset( self::$cache[ $cache_key ] ) ) {
2789 + return self::$cache[ $cache_key ];
2790 + }
2791 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2792 + if ( ! $results ) {
2793 + global $wpdb;
2794 + $sql = "SELECT t.term_id, t.name
2220 2795 FROM {$wpdb->terms} t
2221 2796 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2222 2797 WHERE tt.taxonomy = 'product_cat'";
2223 2798
2224 - if ( $search_query ) {
2225 - $sql .= ' AND t.name LIKE %s';
2226 - $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // Escaping and wildcard
2227 - } else {
2228 - $prepared_sql = $sql; // No need for placeholders
2799 + if ( $search_query ) {
2800 + $sql .= ' AND t.name LIKE %s';
2801 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2802 + } else {
2803 + $prepared_sql = $sql; // No need for placeholders.
2804 + }
2805 +
2806 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2807 + // Cache the results in the object cache for future use.
2808 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2809 + Cache::set_data_cache_key( $cache_key );
2229 2810 }
2230 2811
2231 - $results = $wpdb->get_results( $prepared_sql );
2812 + $cats = [];
2813 + if ( ! is_array( $results ) && ! count( $results ) ) {
2814 + return $cats;
2815 + }
2816 + foreach ( $results as $row ) {
2817 + $category_id = $row->term_id;
2818 + $category_title = $row->name;
2232 2819
2820 + $cats[] = [
2821 + 'value' => $category_id,
2822 + 'label' => $category_title,
2823 + ];
2824 + }
2825 +
2826 + // Cache the results in a static variable for the next call.
2827 + self::$cache[ $cache_key ] = $cats;
2828 + return $cats;
2829 + }
2830 + /**
2831 + * Get WooCommerce product categories.
2832 + *
2833 + * @param string|null $search_query The search string for category names.
2834 + *
2835 + * @return array An array of product categories with 'value' and 'label' keys.
2836 + */
2837 + public static function products_tags_query( $search_query = null ) {
2838 + if ( ! is_admin() ) {
2839 + return [];
2840 + }
2841 +
2842 + $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2843 +
2844 + if ( isset( self::$cache[ $cache_key ] ) ) {
2845 + return self::$cache[ $cache_key ];
2846 + }
2847 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
2848 + if ( ! $results ) {
2849 + global $wpdb;
2850 + $sql = "SELECT t.term_id, t.name
2851 + FROM {$wpdb->terms} t
2852 + JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
2853 + WHERE tt.taxonomy = 'product_tag'";
2854 +
2855 + if ( $search_query ) {
2856 + $sql .= ' AND t.name LIKE %s';
2857 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2858 + } else {
2859 + $prepared_sql = $sql; // No need for placeholders.
2860 + }
2861 +
2862 + $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2863 + // Cache the results in the object cache for future use.
2864 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
2865 + Cache::set_data_cache_key( $cache_key );
2866 + }
2867 +
2233 2868 $cats = [];
2234 2869 if ( ! is_array( $results ) && ! count( $results ) ) {
2235 2870 return $cats;
2236 2871 }
@@ -2243,18 +2878,20 @@
2243 2878 'label' => $category_title,
2244 2879 ];
2245 2880 }
2246 2881
2882 + // Cache the results in a static variable for the next call.
2883 + self::$cache[ $cache_key ] = $cats;
2247 2884 return $cats;
2248 2885 }
2249 2886
2250 2887 /**
2251 - * @param $search_query
2888 + * @param string $search_query Search query.
2252 2889 *
2253 2890 * @return array
2254 2891 */
2255 2892 public static function get_registered_post_types( $search_query = null ) {
2256 - // Get all registered post types
2893 + // Get all registered post types.
2257 2894 $registered_post_types = get_post_types(
2258 2895 [
2259 2896 'public' => true,
2260 2897 '_builtin' => false,
@@ -2267,11 +2904,11 @@
2267 2904 unset( $registered_post_types['rtsb_builder'] );
2268 2905
2269 2906 $post_types = [];
2270 2907
2271 - // Check if a search query is provided
2908 + // Check if a search query is provided.
2272 2909 if ( ! empty( $search_query ) ) {
2273 - // Filter post types based on the search query
2910 + // Filter post types based on the search query.
2274 2911 $registered_post_types = array_filter(
2275 2912 $registered_post_types,
2276 2913 function ( $post_type ) use ( $search_query ) {
2277 2914 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
@@ -2276,9 +2913,9 @@
2276 2913 function ( $post_type ) use ( $search_query ) {
2277 2914 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
2278 2915 }
2279 2916 );
2280 - // Check if 'post' match the search query and include them if they do
2917 + // Check if 'post' match the search query and include them if they do.
2281 2918 if ( stripos( 'post', $search_query ) !== false ) {
2282 2919 $post_types[] = [
2283 2920 'value' => 'post',
2284 2921 'label' => 'Post',
@@ -2290,9 +2927,9 @@
2290 2927 'label' => 'Post',
2291 2928 ];
2292 2929 }
2293 2930
2294 - // Convert the filtered post types to an array
2931 + // Convert the filtered post types to an array.
2295 2932 foreach ( $registered_post_types as $post_type ) {
2296 2933 $post_types[] = [
2297 2934 'value' => $post_type->name,
2298 2935 'label' => $post_type->labels->singular_name,
@@ -2304,9 +2941,10 @@
2304 2941
2305 2942 /**
2306 2943 * Get Post Types.
2307 2944 *
2308 - * @param $search_query
2945 + * @param string $search_query Search query.
2946 + * @param array $args Arguments.
2309 2947 *
2310 2948 * @return array
2311 2949 */
2312 2950 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -2329,11 +2967,12 @@
2329 2967 $query_results = get_posts( $args );
2330 2968 foreach ( $query_results as $post ) {
2331 2969 $posts[] = [
2332 2970 'value' => $post->ID,
2333 - 'label' => $post->post_title,
2971 + 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2334 2972 ];
2335 2973 }
2974 +
2336 2975 if ( $search_query ) {
2337 2976 if ( strpos( '-error 404', $search_query ) ) {
2338 2977 $posts[] = [
2339 2978 'value' => 'error',
@@ -2340,12 +2979,14 @@
2340 2979 'label' => __( '404 Error', 'shopbuilder' ),
2341 2980 ];
2342 2981 }
2343 2982 } else {
2344 - $posts[] = [
2345 - 'value' => 'error',
2346 - 'label' => __( '404 Error', 'shopbuilder' ),
2347 - ];
2983 + if ( 'page' === $args['post_type'] ) {
2984 + $posts[] = [
2985 + 'value' => 'error',
2986 + 'label' => __( '404 Error', 'shopbuilder' ),
2987 + ];
2988 + }
2348 2989 }
2349 2990 } else {
2350 2991 $posts = self::get_registered_post_types( $search_query );
2351 2992 }
@@ -2359,6 +3000,1062 @@
2359 3000 * @return array
2360 3001 */
2361 3002 public static function get_elementor_breakpoints() {
2362 3003 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
3004 + }
3005 +
3006 + /**
3007 + * Render view.
3008 + *
3009 + * @param string $viewName View name.
3010 + * @param array $args View args.
3011 + * @param boolean $return View return.
3012 + * @return string|void
3013 + */
3014 + public static function renderView( $viewName, $args = [], $return = false ) {
3015 + $viewName = str_replace( '.', '/', $viewName );
3016 +
3017 + if ( ! empty( $args ) && is_array( $args ) ) {
3018 + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
3019 + }
3020 +
3021 + $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
3022 +
3023 + if ( ! file_exists( $view_file ) ) {
3024 + _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
3025 +
3026 + return;
3027 + }
3028 +
3029 + if ( $return ) {
3030 + ob_start();
3031 + include $view_file;
3032 +
3033 + return ob_get_clean();
3034 + } else {
3035 + include $view_file;
3036 + }
3037 + }
3038 +
3039 + /**
3040 + * Best selling product query.
3041 + *
3042 + * @param int $minimum_sale Minimum sale.
3043 + * @param int $limit Total limit.
3044 + *
3045 + * @return array|mixed
3046 + */
3047 + public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3048 + $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
3049 + // Check if the data is in the cache.
3050 + if ( isset( self::$cache[ $cache_key ] ) ) {
3051 + return self::$cache[ $cache_key ];
3052 + }
3053 + $cached_data = get_transient( $cache_key );
3054 + if ( $cached_data ) {
3055 + self::$cache[ $cache_key ] = $cached_data;
3056 + return $cached_data;
3057 + }
3058 + global $wpdb;
3059 + $sql = $wpdb->prepare(
3060 + "
3061 + SELECT ID
3062 + FROM {$wpdb->posts} AS p
3063 + LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3064 + WHERE p.post_type = 'product'
3065 + AND p.post_status = 'publish'
3066 + AND pm.meta_key = 'total_sales'
3067 + AND pm.meta_value >= %d
3068 + ORDER BY pm.meta_value + 0 DESC
3069 + LIMIT %d
3070 + ",
3071 + $minimum_sale,
3072 + $limit
3073 + );
3074 + $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
3075 + // Cache the result for future use.
3076 + set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
3077 + self::$cache[ $cache_key ] = $best_selling;
3078 + return $best_selling;
3079 + }
3080 +
3081 + /**
3082 + * @param null|Object $product Product Object.
3083 + * @param int $days_threshold Date String.
3084 + *
3085 + * @return bool
3086 + */
3087 + public static function is_new_product( $product = null, $days_threshold = 30 ) {
3088 + if ( is_null( $product ) ) {
3089 + global $product;
3090 + }
3091 + if ( ! $product instanceof WC_Product ) {
3092 + return false;
3093 + }
3094 +
3095 + $product_id = $product->get_id();
3096 + $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
3097 + $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
3098 +
3099 + if ( isset( self::$cache[ $cache_key ] ) ) {
3100 + return self::$cache[ $cache_key ];
3101 + }
3102 + $date_created = $product->get_date_created();
3103 + if ( ! $date_created ) {
3104 + return false;
3105 + }
3106 + $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
3107 +
3108 + // Calculate the difference in days.
3109 + $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
3110 +
3111 + // Check if the product is considered new.
3112 + $is_new = $days_difference <= $days_threshold;
3113 +
3114 + // Cache the result for a day.
3115 + self::$cache[ $cache_key ] = $is_new;
3116 +
3117 + return $is_new;
3118 + }
3119 +
3120 + /**
3121 + * Checks if a feature is disabled for guest users based on the provided option.
3122 + *
3123 + * @param string $option The option to retrieve from the item.
3124 + * @param mixed $default The default value if the option is not found.
3125 + *
3126 + * @return bool
3127 + */
3128 + public static function is_guest_feature_disabled( $option, $default ) {
3129 + $disabled = self::get_option( 'general', 'guest_user', $option, $default );
3130 +
3131 + return ! is_user_logged_in() && $disabled;
3132 + }
3133 +
3134 + /**
3135 + * Checks if a feature is disabled for guest users based on the provided option.
3136 + *
3137 + * @return void
3138 + */
3139 + public static function woocommerce_output_all_notices() {
3140 + if ( function_exists( 'wc_print_notices' ) ) :
3141 + echo '<div class="rtsb-notice">';
3142 + woocommerce_output_all_notices();
3143 + echo '</div>';
3144 + endif;
3145 + }
3146 +
3147 + /**
3148 + * @param object $product Product.
3149 + * @return bool
3150 + */
3151 + public static function is_visible_qty_input( $product = null ) {
3152 + $is_visible_qty = true;
3153 + if ( $product instanceof \WC_Product ) {
3154 + if ( $product->is_sold_individually() ) {
3155 + $is_visible_qty = false;
3156 + } elseif ( $product->managing_stock() ) {
3157 + if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3158 + $is_visible_qty = true;
3159 + } elseif ( $product->get_stock_quantity() < 2 ) {
3160 + $is_visible_qty = false;
3161 + }
3162 + }
3163 + }
3164 + return $is_visible_qty;
3165 + }
3166 +
3167 + /**
3168 + * @param int $object_id Object id.
3169 + * @param string $element_type element type.
3170 + * @param string|null $current_lang null for current language, default for default languages.
3171 + * @return false|mixed|null
3172 + */
3173 + public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3174 + if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3175 + return $object_id;
3176 + }
3177 + if ( ! $object_id || ! $element_type ) {
3178 + return $object_id;
3179 + }
3180 + if ( 'default' === $current_lang ) {
3181 + $lang = apply_filters( 'wpml_default_language', null );
3182 + } else {
3183 + $lang = null;
3184 + }
3185 + return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3186 + }
3187 +
3188 + /**
3189 + * @return string
3190 + */
3191 + public static function wpml_current_language() {
3192 + $current = apply_filters( 'wpml_current_language', null );
3193 + $default = apply_filters( 'wpml_default_language', null );
3194 + return $default !== $current ? $current : null;
3195 + }
3196 +
3197 + /**
3198 + * Custom icons.
3199 + *
3200 + * @return string[]
3201 + */
3202 + public static function get_custom_icon_names() {
3203 + return [
3204 + 'heart-empty',
3205 + 'heart',
3206 + 'eye',
3207 + 'exchange',
3208 + 'plus',
3209 + 'minus',
3210 + 'avatar',
3211 + 'pay',
3212 + 'share',
3213 + 'clock',
3214 + 'check-alt',
3215 + 'check',
3216 + 'delete',
3217 + 'marker',
3218 + 'list',
3219 + 'list-2',
3220 + 'power',
3221 + 'cart',
3222 + 'cart-2',
3223 + 'cart-3',
3224 + 'downloads',
3225 + 'zoom',
3226 + 'user-edit',
3227 + 'grid',
3228 + 'filter',
3229 + 'billing',
3230 + 'login',
3231 + 'payment',
3232 + 'search',
3233 + 'edit',
3234 + 'coupon',
3235 + 'arrows-cw',
3236 + 'trash-empty',
3237 + ];
3238 + }
3239 +
3240 + /**
3241 + * Retrieve an array of custom icons with their HTML representation.
3242 + *
3243 + * @return array
3244 + */
3245 + public static function get_icons() {
3246 + $icon = self::get_custom_icon_names();
3247 + $icons_array = [];
3248 + foreach ( $icon as $value ) {
3249 + $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3250 + }
3251 + return $icons_array;
3252 + }
3253 +
3254 + /**
3255 + * Generates HTML markup for displaying an endpoint icon.
3256 + *
3257 + * @param array $data The endpoint data containing icon information.
3258 + * @param boolean $wrapper Whether to wrap the icon in a span element.
3259 + *
3260 + * @return string
3261 + */
3262 + public static function get_icon_html( $data, $wrapper = true ) {
3263 + if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3264 + return '';
3265 + }
3266 + $endpoint = '';
3267 + $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3268 +
3269 + if ( 'select_icon' === $data['icon_source'] ) {
3270 + $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3271 + } else {
3272 + $icon_html = '';
3273 + $icon_url = $data['image_icon']['source'] ?? '';
3274 + $icon_id = $data['image_icon']['id'] ?? 0;
3275 + $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3276 +
3277 + if ( 'svg' === $extension ) {
3278 + $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3279 + $is_svg = strpos( $content, '<svg' );
3280 +
3281 + if ( false !== $is_svg ) {
3282 + $icon_html = substr( $content, $is_svg );
3283 + }
3284 + } else {
3285 + $attr = [
3286 + 'class' => 'rtsb-icon-img',
3287 + 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3288 + ];
3289 +
3290 + $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3291 + }
3292 +
3293 + $html .= $icon_html;
3294 + }
3295 +
3296 + $html .= $wrapper ? '</span>' : '';
3297 +
3298 + return $html;
3299 + }
3300 + /**
3301 + * Flushes rewrite rules.
3302 + *
3303 + * @return void
3304 + */
3305 + public static function maybe_flush_rewrite_rules() {
3306 + add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3307 + add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3308 + }
3309 +
3310 + /**
3311 + * Flushes rewrite rules if needed.
3312 + *
3313 + * @return void
3314 + */
3315 + public static function flush_rewrite_rules() {
3316 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3317 + flush_rewrite_rules();
3318 + }
3319 +
3320 + if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3321 + flush_rewrite_rules();
3322 + delete_option( 'rtsb_permalinks_flushed' );
3323 + }
3324 + }
3325 +
3326 + /**
3327 + * Flushes rewrite rules if needed for second time.
3328 + *
3329 + * @return void
3330 + */
3331 + public static function flush_rewrite_rules_once_more() {
3332 + if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3333 + flush_rewrite_rules();
3334 + update_option( 'rtsb_permalinks_flushed', 'yes' );
3335 + delete_option( 'rtsb_permalinks_need_flush' );
3336 + }
3337 + }
3338 +
3339 + /**
3340 + * Social Share Platforms.
3341 + *
3342 + * @param string $section_id Section ID.
3343 + * @param string $block_id Block ID.
3344 + * @param string $option_key Option key.
3345 + * @param string $compare_key Compare key.
3346 + * @param string $compare_value Compare value.
3347 + * @param array $values Values.
3348 + *
3349 + * @return void
3350 + */
3351 + public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3352 + $modules = DataModel::source()->get_option( $section_id, [], false );
3353 + if ( empty( $modules[ $block_id ] ) ) {
3354 + return;
3355 + }
3356 + $options = $modules[ $block_id ];
3357 + if ( empty( $options[ $option_key ] ) ) {
3358 + return;
3359 + }
3360 + $repeater_options = json_decode( $options[ $option_key ], true );
3361 + // Check if options are not empty and are in array format.
3362 + if ( ! is_array( $repeater_options ) ) {
3363 + return;
3364 + }
3365 + // Use array_column to get an array of values from $compare_key.
3366 + $column_values = array_column( $repeater_options, $compare_key );
3367 + // Use array_search to find the index of the $compare_value.
3368 + $index = array_search( $compare_value, $column_values, true );
3369 + if ( false === $index ) {
3370 + return;
3371 + }
3372 + $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3373 + $options[ $option_key ] = wp_json_encode( $repeater_options );
3374 + $modules[ $block_id ] = $options;
3375 + DataModel::source()->set_option( $section_id, $modules );
3376 + }
3377 +
3378 +
3379 + /**
3380 + * @param array $ids products id.
3381 + * @return array
3382 + */
3383 + public static function get_available_products_by_ids( $ids = [] ) {
3384 + $ids = array_filter(
3385 + $ids,
3386 + function ( $id ) {
3387 + return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3388 + }
3389 + );
3390 + return $ids;
3391 + }
3392 +
3393 + /**
3394 + * Generate and cache dynamic CSS styles.
3395 + *
3396 + * @param array $options CSS options to apply (e.g. padding, margin).
3397 + * @param string $cache_key Cache key for the CSS.
3398 + * @param array $css_properties An array of CSS properties with selectors and properties.
3399 + *
3400 + * @return void
3401 + */
3402 + public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3403 + $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3404 + $style_handle = self::optimized_handle( 'rtsb-frontend' );
3405 +
3406 + if ( false !== $cached_css ) {
3407 + wp_add_inline_style( $style_handle, $cached_css );
3408 + return;
3409 + }
3410 +
3411 + $css_rules = [];
3412 + $grouped_css_rules = [];
3413 +
3414 + foreach ( $css_properties as $option => $details ) {
3415 + if ( ! empty( $options[ $option ] ) ) {
3416 + $selector = $details['selector'] ?? '';
3417 + $property = $details['property'] ?? '';
3418 + $unit = $details['unit'] ?? '';
3419 + $value = $options[ $option ];
3420 +
3421 + if ( empty( $grouped_css_rules[ $selector ] ) ) {
3422 + $grouped_css_rules[ $selector ] = [];
3423 + }
3424 +
3425 + if ( is_array( $property ) ) {
3426 + foreach ( $property as $prop ) {
3427 + $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3428 + }
3429 + } else {
3430 + $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3431 + }
3432 + }
3433 + }
3434 +
3435 + foreach ( $grouped_css_rules as $selector => $properties ) {
3436 + $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3437 + }
3438 +
3439 + $dynamic_css = implode( ' ', $css_rules );
3440 +
3441 + wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3442 + Cache::set_data_cache_key( $cache_key );
3443 +
3444 + if ( ! empty( $dynamic_css ) ) {
3445 + wp_add_inline_style( $style_handle, $dynamic_css );
3446 + }
3447 + }
3448 +
3449 + /**
3450 + * Pro version notice.
3451 + *
3452 + * @param string $ver Pro Version.
3453 + * @param string $tab Tab.
3454 + * @param string $name Name.
3455 + * @param bool $enable_free_Link Enable free link.
3456 + *
3457 + * @return array[]
3458 + */
3459 + public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3460 + return [
3461 + 'version_check' => [
3462 + 'id' => 'version_check',
3463 + 'type' => 'title',
3464 + 'label' => sprintf(
3465 + /* translators: 1: required version, 2: link to the Plugins page */
3466 + esc_html__(
3467 + 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3468 + 'shopbuilder'
3469 + ),
3470 + $name,
3471 + sprintf(
3472 + '<br /><u><b>%s</b></u>',
3473 + esc_html( $ver ?? '1.5.0' ) . ' or higher'
3474 + ),
3475 + $enable_free_Link
3476 + ? sprintf(
3477 + '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3478 + esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3479 + esc_url( admin_url( 'plugins.php' ) ),
3480 + esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3481 + esc_html__( 'from the Plugins page', 'shopbuilder' )
3482 + )
3483 + : ''
3484 + ),
3485 + 'tab' => $tab,
3486 + 'customClass' => 'checkout-notice',
3487 + ],
3488 + ];
3489 + }
3490 +
3491 + /**
3492 + * Get product gallery ids.
3493 + *
3494 + * @param object $product Product object.
3495 + *
3496 + * @return mixed
3497 + */
3498 + public static function get_cached_gallery_ids( $product ) {
3499 + $product_id = $product->get_id();
3500 + $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3501 +
3502 + if ( isset( self::$cache[ $cache_key ] ) ) {
3503 + return self::$cache[ $cache_key ];
3504 + }
3505 +
3506 + $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3507 +
3508 + if ( false !== $cached_result ) {
3509 + self::$cache[ $cache_key ] = $cached_result;
3510 +
3511 + return $cached_result;
3512 + }
3513 +
3514 + $gallery_ids = $product->get_gallery_image_ids();
3515 + self::$cache[ $cache_key ] = $gallery_ids;
3516 +
3517 + wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3518 + Cache::set_data_cache_key( $cache_key );
3519 +
3520 + return $gallery_ids;
3521 + }
3522 +
3523 + /**
3524 + * Check if optimization settings are enabled.
3525 + *
3526 + * @return bool
3527 + */
3528 + public static function is_optimization_enabled() {
3529 + $has_pro = rtsb()->has_pro();
3530 + $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
3531 +
3532 + if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
3533 + return false;
3534 + }
3535 +
3536 + $generalList = GeneralList::instance()->get_data();
3537 +
3538 + return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
3539 + }
3540 +
3541 + /**
3542 + * Get the optimized handle.
3543 + *
3544 + * @param string $handle Base handle used for script/style IDs.
3545 + *
3546 + * @return string
3547 + */
3548 + public static function optimized_handle( $handle ) {
3549 + $use_optimization = self::is_optimization_enabled();
3550 +
3551 + if ( $use_optimization ) {
3552 + $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
3553 + }
3554 +
3555 + return $handle;
3556 + }
3557 +
3558 + /**
3559 + * Get the optimized handle by context.
3560 + *
3561 + * @return string
3562 + */
3563 + public static function get_optimized_handle_by_context() {
3564 + $context = self::detect_context();
3565 +
3566 + if ( 'account' === $context ) {
3567 + return 'rtsb-bundled-global';
3568 + }
3569 +
3570 + return "rtsb-bundled-$context";
3571 + }
3572 +
3573 + /**
3574 + * Check if Elementor page.
3575 + *
3576 + * @param int $post_id Post ID.
3577 + *
3578 + * @return bool
3579 + */
3580 + public static function is_elementor_page( $post_id = null ) {
3581 + if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
3582 + return false;
3583 + }
3584 +
3585 + $post_id = $post_id ?: get_the_ID();
3586 +
3587 + if ( ! $post_id ) {
3588 + return true;
3589 + }
3590 +
3591 + return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
3592 + }
3593 +
3594 + /**
3595 + * Check if shop or archive.
3596 + *
3597 + * @return bool
3598 + */
3599 + public static function is_shop_or_archive() {
3600 + return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
3601 + }
3602 +
3603 + /**
3604 + * Detect context.
3605 + *
3606 + * @return string
3607 + */
3608 + public static function detect_context() {
3609 + switch ( true ) {
3610 + case is_product() || BuilderFns::is_product():
3611 + $context = 'product';
3612 + break;
3613 +
3614 + case is_cart() || BuilderFns::is_cart():
3615 + $context = 'cart';
3616 + break;
3617 +
3618 + case is_checkout() || BuilderFns::is_checkout():
3619 + $context = 'checkout';
3620 + break;
3621 +
3622 + case self::is_shop_or_archive():
3623 + $context = 'shop';
3624 + break;
3625 +
3626 + default:
3627 + $context = 'global';
3628 + break;
3629 + }
3630 +
3631 + return apply_filters( 'rtsb/optimizer/context_detect', $context );
3632 + }
3633 +
3634 + /**
3635 + * Enqueue optimized assets.
3636 + *
3637 + * @return void
3638 + */
3639 + public static function enqueue_optimized_assets() {
3640 + $asset_registry = AssetRegistry::instance();
3641 + $bundled_assets = $asset_registry->get_bundled_assets();
3642 + $context = self::detect_context();
3643 + $contextual_loading = self::is_contextual_loading();
3644 + $theme_handle = self::find_theme_stylesheet_handle();
3645 +
3646 + if ( $contextual_loading ) {
3647 + // Enqueue JS.
3648 + if ( ! empty( $bundled_assets['js'] ) ) {
3649 + $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
3650 +
3651 + if ( $js_context ) {
3652 + wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
3653 + }
3654 + }
3655 +
3656 + // Enqueue CSS.
3657 + if ( ! empty( $bundled_assets['css'] ) ) {
3658 + $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
3659 +
3660 + if ( $css_context ) {
3661 + $handle = $bundled_assets['css'][ $css_context ]['handle'];
3662 +
3663 + wp_enqueue_style( $handle );
3664 +
3665 + if ( ! empty( $theme_handle ) ) {
3666 + self::set_theme_dependency( $theme_handle, $handle );
3667 + }
3668 + }
3669 + }
3670 + } else {
3671 + // Enqueue JS.
3672 + if ( ! empty( $bundled_assets['js'] ) ) {
3673 + wp_enqueue_script( $bundled_assets['js']['handle'] );
3674 + }
3675 +
3676 + // Enqueue CSS.
3677 + if ( ! empty( $bundled_assets['css'] ) ) {
3678 + $handle = $bundled_assets['css']['handle'];
3679 +
3680 + wp_enqueue_style( $handle );
3681 +
3682 + if ( ! empty( $theme_handle ) ) {
3683 + self::set_theme_dependency( $theme_handle, $handle );
3684 + }
3685 + }
3686 + }
3687 + }
3688 +
3689 + /**
3690 + * Find theme stylesheet handle.
3691 + *
3692 + * @return string|false
3693 + */
3694 + private static function find_theme_stylesheet_handle() {
3695 + static $cached_handle = null;
3696 +
3697 + if ( null !== $cached_handle ) {
3698 + return $cached_handle;
3699 + }
3700 +
3701 + global $wp_styles;
3702 +
3703 + if ( ! $wp_styles instanceof WP_Styles ) {
3704 + return false;
3705 + }
3706 +
3707 + $stylesheet_uri = get_stylesheet_uri();
3708 + $theme_directory_uri = get_stylesheet_directory_uri();
3709 +
3710 + $hash = md5( $stylesheet_uri );
3711 + $transient_key = 'rtsb_theme_css_handle_' . $hash;
3712 +
3713 + $transient = get_transient( $transient_key );
3714 +
3715 + if ( false !== $transient ) {
3716 + $cached_handle = $transient;
3717 +
3718 + return $transient;
3719 + }
3720 +
3721 + foreach ( $wp_styles->registered as $handle => $style ) {
3722 + $src = $style->src;
3723 +
3724 + if (
3725 + ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
3726 + strpos( $src, 'style.css' ) !== false
3727 + ) {
3728 + set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3729 + Cache::set_transient_cache_key( $transient_key );
3730 + $cached_handle = $handle;
3731 +
3732 + return $handle;
3733 + }
3734 + }
3735 +
3736 + $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
3737 +
3738 + if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
3739 + set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3740 + Cache::set_transient_cache_key( $transient_key );
3741 + $cached_handle = $filtered_handle;
3742 +
3743 + return $filtered_handle;
3744 + }
3745 +
3746 + set_transient( $transient_key, false, DAY_IN_SECONDS );
3747 + Cache::set_transient_cache_key( $transient_key );
3748 + $cached_handle = false;
3749 +
3750 + return false;
3751 + }
3752 +
3753 + /**
3754 + * Set theme dependency.
3755 + *
3756 + * @param string $theme_handle Theme handle.
3757 + * @param string $our_handle Our handle.
3758 + *
3759 + * @return void
3760 + */
3761 + private static function set_theme_dependency( $theme_handle, $our_handle ) {
3762 + global $wp_styles;
3763 +
3764 + if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
3765 + return;
3766 + }
3767 +
3768 + $theme_style = $wp_styles->registered[ $theme_handle ];
3769 +
3770 + // Add our handle as a dependency if it's not already there.
3771 + if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
3772 + $theme_style->deps[] = $our_handle;
3773 + }
3774 + }
3775 +
3776 + /**
3777 + * Check if Elementor scripts should be loaded.
3778 + *
3779 + * @return bool
3780 + */
3781 + public static function should_load_elementor_scripts() {
3782 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3783 +
3784 + $enable_optimization = $data['enable_optimization'] ?? 'on';
3785 + $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
3786 +
3787 + if ( 'on' !== $enable_optimization ) {
3788 + return true;
3789 + }
3790 +
3791 + return 'on' === $load_elementor_scripts;
3792 + }
3793 +
3794 + /**
3795 + * Locate asset.
3796 + *
3797 + * @param string $relative_path Relative path.
3798 + *
3799 + * @return string|null
3800 + */
3801 + public static function locate_asset( $relative_path ) {
3802 + $free_context = rtsb();
3803 + $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
3804 +
3805 + $paths = [];
3806 +
3807 + if ( $pro_context ) {
3808 + $paths[] = $pro_context->get_assets_path( $relative_path );
3809 + }
3810 +
3811 + $paths[] = $free_context->get_assets_path( $relative_path );
3812 +
3813 + foreach ( $paths as $path ) {
3814 + if ( file_exists( $path ) ) {
3815 + return $path;
3816 + }
3817 + }
3818 +
3819 + return null;
3820 + }
3821 +
3822 + /**
3823 + * Enqueue module assets.
3824 + *
3825 + * @param string $handle Asset handle.
3826 + * @param string $module_name Module name.
3827 + * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
3828 + *
3829 + * @return string
3830 + */
3831 + public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
3832 + $use_optimization = self::is_optimization_enabled();
3833 + $handle = self::optimized_handle( $handle );
3834 +
3835 + if ( $use_optimization ) {
3836 + return $handle;
3837 + }
3838 +
3839 + $defaults = [
3840 + 'type' => 'both',
3841 + 'deps' => [ 'jquery', 'rtsb-public' ],
3842 + 'context' => null,
3843 + 'version' => RTSB_VERSION,
3844 + ];
3845 +
3846 + $config = array_merge( $defaults, $options );
3847 + $rtl_suffix = is_rtl() ? '-rtl' : '';
3848 + $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
3849 + $context = $config['context'] ?: rtsb();
3850 + $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
3851 + $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
3852 +
3853 + // Register CSS if enabled.
3854 + if ( $load_css ) {
3855 + wp_register_style(
3856 + $handle,
3857 + $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
3858 + [],
3859 + $config['version']
3860 + );
3861 + }
3862 +
3863 + // Register JS if enabled.
3864 + if ( $load_js ) {
3865 + wp_register_script(
3866 + $handle,
3867 + $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
3868 + $config['deps'],
3869 + $config['version'],
3870 + true
3871 + );
3872 + }
3873 +
3874 + if ( $load_css ) {
3875 + wp_enqueue_style( $handle );
3876 +
3877 + $theme_handle = self::find_theme_stylesheet_handle();
3878 +
3879 + if ( ! empty( $theme_handle ) ) {
3880 + self::set_theme_dependency( $theme_handle, $handle );
3881 + }
3882 + }
3883 +
3884 + if ( $load_js ) {
3885 + wp_enqueue_script( $handle );
3886 + }
3887 +
3888 + return $handle;
3889 + }
3890 +
3891 + /**
3892 + * Check if contextual loading is enabled.
3893 + *
3894 + * @return bool
3895 + */
3896 + public static function is_contextual_loading() {
3897 + $data = GeneralList::instance()->get_data()['optimization'] ?? [];
3898 +
3899 + return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
3900 + }
3901 +
3902 + /**
3903 + * Get Modules list with cache support.
3904 + *
3905 + * @return array
3906 + */
3907 + public static function get_modules_list() {
3908 + static $cached_modules = null;
3909 +
3910 + if ( null !== $cached_modules ) {
3911 + return $cached_modules;
3912 + }
3913 +
3914 + $cache_key = 'rtsb_module_list';
3915 + $cache_group = 'shopbuilder';
3916 +
3917 + $cached = wp_cache_get( $cache_key, $cache_group );
3918 +
3919 + if ( false !== $cached ) {
3920 + $cached_modules = $cached;
3921 +
3922 + return $cached;
3923 + }
3924 +
3925 + $modules = ModuleList::instance()->get_data();
3926 +
3927 + wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
3928 + Cache::set_data_cache_key( $cache_key );
3929 +
3930 + $cached_modules = $modules;
3931 +
3932 + return $modules;
3933 + }
3934 +
3935 + /**
3936 + * Get Elementor widgets list with cache support.
3937 + *
3938 + * @return array
3939 + */
3940 + public static function get_widgets_list() {
3941 + static $cached_widgets = null;
3942 +
3943 + if ( null !== $cached_widgets ) {
3944 + return $cached_widgets;
3945 + }
3946 +
3947 + $cache_key = 'rtsb_elementor_widget_list';
3948 + $cache_group = 'shopbuilder';
3949 +
3950 + $cached = wp_cache_get( $cache_key, $cache_group );
3951 +
3952 + if ( false !== $cached ) {
3953 + $cached_widgets = $cached;
3954 +
3955 + return $cached;
3956 + }
3957 +
3958 + $widgets = ElementList::instance()->get_list();
3959 +
3960 + wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
3961 + Cache::set_data_cache_key( $cache_key );
3962 +
3963 + $cached_widgets = $widgets;
3964 +
3965 + return $widgets;
3966 + }
3967 +
3968 + /**
3969 + * Convert the given price to the active currency.
3970 + *
3971 + * @param float $price The original price.
3972 + * @param Object||null $product Product.
3973 + *
3974 + * @return float
3975 + */
3976 + public static function get_currency_base_price( $price, $product = null ) {
3977 + return apply_filters( 'rtsb/convert/currency/price', $price, $product );
3978 + }
3979 + /**
3980 + * Generate a signed URL with a payload.
3981 + *
3982 + * @param array $payload Associative data to encode.
3983 + * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
3984 + * @param string $key Key name to use in the URL.
3985 + * @return string Signed URL with key parameter.
3986 + */
3987 + public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
3988 + if ( empty( $key ) ) {
3989 + $key = 'key';
3990 + }
3991 + // Convert payload to JSON.
3992 + $data = wp_json_encode( $payload );
3993 + // Create signature.
3994 + $signature = wp_hash( $data );
3995 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
3996 + $encode_key = base64_encode( $data . '::' . $signature );
3997 + // Return full URL with key param.
3998 + return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
3999 + }
4000 +
4001 + /**
4002 + * Decode and verify a signed URL key.
4003 + *
4004 + * @param string $key Encoded key from URL.
4005 + * @return array|false Decoded payload array on success, false on failure.
4006 + */
4007 + public static function decode_signed_key( string $key ) {
4008 + if ( empty( $key ) ) {
4009 + return false;
4010 + }
4011 + $key = rawurldecode( wp_unslash( $key ) );
4012 + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
4013 + $raw = base64_decode( sanitize_text_field( $key ) );
4014 + if ( ! $raw ) {
4015 + return false;
4016 + }
4017 + $parts = explode( '::', $raw, 2 );
4018 + if ( count( $parts ) !== 2 ) {
4019 + return false;
4020 + }
4021 + list( $data_json, $signature ) = $parts;
4022 + // Validate signature.
4023 + if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4024 + return false;
4025 + }
4026 + $payload = json_decode( $data_json, true );
4027 + return is_array( $payload ) ? $payload : false;
4028 + }
4029 +
4030 + /**
4031 + * Get Loco Translate MO file path for a plugin.
4032 + *
4033 + * @param string $textdomain Plugin textdomain.
4034 + * @return void|false
4035 + */
4036 + public static function load_loco_textdomain( $textdomain ) {
4037 + if ( ! function_exists( 'loco_plugin_version' ) ) {
4038 + return false;
4039 + }
4040 + $lang = WP_LANG_DIR;
4041 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4042 + if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4043 + $lang = LOCO_LANG_DIR;
4044 + $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4045 + }
4046 + if ( ! file_exists( $path ) ) {
4047 + if ( 'shopbuilder' === $textdomain ) {
4048 + $plugin_root = dirname( RTSB_FILE );
4049 + } elseif ( 'shopbuilder-pro' === $textdomain ) {
4050 + $plugin_root = dirname( RTSBPRO_FILE );
4051 + } else {
4052 + return false;
4053 + }
4054 + $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4055 + }
4056 + if ( ! file_exists( $path ) ) {
4057 + return false;
4058 + }
4059 + load_textdomain( $textdomain, $path );
2363 4060 }
2364 4061 }