PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 2.0.2
ShopBuilder – WooCommerce Builder For Elementor v2.0.2
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 +205 -2437 3.4.2 → 2.0.2 View file →
@@ -11,24 +11,14 @@
11 11 if ( ! defined( 'ABSPATH' ) ) {
12 12 exit( 'This script cannot be accessed directly.' );
13 13 }
14 14
15 -
16 -use RadiusTheme\SB\Dependencies\CodesVault\Howdyqb\DB;
17 -use DateTime;
18 -use WP_Query;
19 -use WP_Styles;
20 -use WC_Product;
21 -use Elementor\Plugin;
22 -use WC_Product_Query;
23 15 use Elementor\Icons_Manager;
24 16 use RadiusTheme\SB\Models\ReSizer;
25 -use RadiusTheme\SB\Models\Settings;
26 17 use RadiusTheme\SB\Models\DataModel;
27 18 use RadiusTheme\SB\Models\ModuleList;
28 -use RadiusTheme\SB\Models\GeneralList;
29 -use RadiusTheme\SB\Models\ElementList;
30 -use RadiusTheme\SB\Controllers\AssetRegistry;
19 +use RadiusTheme\SB\Models\Settings;
20 +use WC_Product;
31 21
32 22 /**
33 23 * Fns class
34 24 */
@@ -34,94 +24,8 @@
34 24 */
35 25 class Fns {
36 26
37 27 /**
38 - * @var array
39 - */
40 - private static $cache = [];
41 -
42 - /**
43 - * Whether asset bundling failed during this request.
44 - *
45 - * Set by AssetRegistry when AssetBundler::build() cannot produce a file, for
46 - * example on the first request after a plugin upgrade while the autoloader
47 - * still points at the removed files, or when the cache directory turns out
48 - * not to be writable. Once set, is_optimization_enabled() reports false for
49 - * the remainder of the request so the callers register and enqueue the plain
50 - * unbundled assets instead of a stylesheet handle with an empty source.
51 - *
52 - * Request scoped on purpose: it must not be persisted, the next request
53 - * retries the build.
54 - *
55 - * @var bool
56 - */
57 - private static $optimization_unavailable = false;
58 -
59 - /**
60 - * Flag that a bundle could not be produced during this request.
61 - *
62 - * @return void
63 - */
64 - public static function mark_optimization_unavailable() {
65 - self::$optimization_unavailable = true;
66 - }
67 -
68 - /**
69 - * Whether bundling has been marked unavailable for this request.
70 - *
71 - * @return bool
72 - */
73 - public static function is_optimization_unavailable() {
74 - return self::$optimization_unavailable;
75 - }
76 -
77 - /**
78 - * Check if the stored license is valid. Its Only Show Pro Label.
79 - *
80 - * @return bool
81 - */
82 - public static function has_valid_license() {
83 - static $cached_result = null;
84 - // Return cached result if already calculated in this request.
85 - if ( null !== $cached_result ) {
86 - return $cached_result;
87 - }
88 - $license_status = rtsb()->has_pro() ? rtsbpro()->get_license( 'license_status' ) : '';
89 - return 'valid' === $license_status;
90 - }
91 -
92 - /**
93 - * Check if license is valid or bypassed by theme,
94 - *
95 - * @return bool
96 - */
97 - public static function is_pro_authorized() {
98 - static $cached_result = null;
99 - if ( null !== $cached_result ) {
100 - return $cached_result;
101 - }
102 - // ❗ Step 2: If Pro not installed → no access.
103 - if ( ! function_exists( 'rtsbpro' ) ) {
104 - $cached_result = false;
105 - return $cached_result;
106 - }
107 - $current_theme = wp_get_theme()->get_stylesheet(); // Child theme And Parent Theme Need Check work or not.
108 - /**
109 - * Allowed themes (bypass license)
110 - *
111 - * @param array $themes
112 - */
113 - $allowed_themes = apply_filters( 'rt_pro_access_allowed_themes', [] );
114 - // ✅ Step 1: Theme bypass (no license check).
115 - if ( in_array( $current_theme, $allowed_themes, true ) ) {
116 - $cached_result = true;
117 - return true;
118 - }
119 - // ✅ Step 3: Normal license validation
120 - $license_status = rtsbpro()->get_license( 'license_status' );
121 - return ( 'valid' === $license_status );
122 - }
123 - /**
124 28 * Verify nonce.
125 29 *
126 30 * @return bool
127 31 */
@@ -135,118 +39,12 @@
135 39 return false;
136 40 }
137 41
138 42 /**
139 - * Get nonce.
43 + * @param $plugin_slug
140 44 *
141 - * @return string|null
142 - */
143 - public static function enable_loader() {
144 - return 'on' !== self::get_option( 'general', 'optimization', 'remove_pre_loader', '' );
145 - }
146 - /**
147 - * Get nonce.
148 - *
149 - * @return string|null
150 - */
151 - public static function content_invisible() {
152 - $wp_rocket_compatibility = 'on' !== self::get_option( 'general', 'optimization', 'wp_rocket_compatibility', '' );
153 - if ( $wp_rocket_compatibility ) {
154 - return true;
155 - }
156 - if ( defined( 'WP_ROCKET_VERSION' ) ) {
157 - return false;
158 - }
159 - return true;
160 - }
161 - /**
162 - * Get nonce.
163 - *
164 - * @return string|null
165 - */
166 - public static function get_nonce() {
167 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
168 - return isset( $_REQUEST[ rtsb()->nonceId ] ) ? sanitize_text_field( $_REQUEST[ rtsb()->nonceId ] ) : null;
169 - }
170 -
171 - /**
172 - * Set Session
173 - *
174 - * @param string $name Name.
175 - * @param mixed $value Value.
176 - */
177 - public static function setSession( $name, $value ) {
178 - if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
179 - session_start();
180 - $_SESSION[ $name ] = $value;
181 - } else {
182 - $_SESSION[ $name ] = $value;
183 - }
184 - }
185 -
186 - /**
187 - * Get Cookie or Session
188 - *
189 - * @param string $name Name.
190 - *
191 45 * @return bool
192 46 */
193 - public static function getSession( $name ) {
194 - if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
195 - session_start();
196 - }
197 - return $_SESSION[ $name ] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
198 - }
199 - /**
200 - * Remove Session Variable
201 - *
202 - * @param string $name The name of the session variable to remove.
203 - */
204 - public static function removeSession( $name ) {
205 - if ( ! headers_sent() && session_status() == PHP_SESSION_NONE ) {
206 - session_start();
207 - unset( $_SESSION[ $name ] );
208 - } else {
209 - unset( $_SESSION[ $name ] );
210 - }
211 - }
212 - /**
213 - * Gets the current timestamp in WordPress timezone.
214 - *
215 - * @return int Current timestamp.
216 - */
217 - public static function currentTimestampUTC() {
218 - $wp_timezone = wp_timezone();
219 - $now = new DateTime( 'now', $wp_timezone ); // Current time in WP timezone.
220 - return $now->getTimestamp();
221 - }
222 - /**
223 - * Action.
224 - *
225 - * @param string $action action.
226 - * @return void
227 - */
228 - public static function add_to_scheduled_hook_list( $action ) {
229 - if ( empty( $action ) ) {
230 - return;
231 - }
232 - $schedule = get_option( 'rtsb_cron_schedule_free', [] );
233 - $schedule[] = $action;
234 - update_option( 'rtsb_cron_schedule_free', array_unique( $schedule ) );
235 - }
236 - /**
237 - * @return DB
238 - */
239 - public static function DB() {
240 - return new DB( 'wpdb' );
241 - }
242 - /**
243 - * Check if a plugin is installed.
244 - *
245 - * @param string $plugin_slug Plugin slug.
246 - *
247 - * @return bool
248 - */
249 47 public static function check_plugin_installed( $plugin_slug ): bool {
250 48 $installed_plugins = get_plugins();
251 49
252 50 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
@@ -252,12 +50,10 @@
252 50 return array_key_exists( $plugin_slug, $installed_plugins ) || in_array( $plugin_slug, $installed_plugins, true );
253 51 }
254 52
255 53 /**
256 - * Check if a plugin is active.
54 + * @param $plugin_slug
257 55 *
258 - * @param string $plugin_slug Plugin slug.
259 - *
260 56 * @return bool
261 57 */
262 58 public static function check_plugin_active( $plugin_slug ): bool {
263 59 if ( is_plugin_active( $plugin_slug ) ) {
@@ -265,87 +61,21 @@
265 61 }
266 62
267 63 return false;
268 64 }
269 - /**
270 - * Get all user roles.
271 - *
272 - * @return array
273 - */
274 - public static function get_current_user_roles() {
275 - $current_user = wp_get_current_user();
276 - return $current_user->roles;
277 - }
278 - /**
279 - * Get all user roles.
280 - *
281 - * @return array|false|mixed
282 - */
283 - public static function get_all_user_roles() {
284 - // Memoized because the settings schema asks for the role list several
285 - // times per request, and the miss path require_once()s an admin-only
286 - // WordPress file. The static also covers the legitimate empty-result
287 - // case, which wp_cache_get() alone cannot distinguish from a miss.
288 - static $memo = null;
289 65
290 - if ( null !== $memo ) {
291 - return $memo;
292 - }
293 -
294 - $cache_key = 'rtsb_get_user_roles';
295 - $roles = wp_cache_get( $cache_key, Cache::group() );
296 -
297 - if ( empty( $roles ) ) {
298 - if ( ! function_exists( 'get_editable_roles' ) ) {
299 - require_once ABSPATH . 'wp-admin/includes/user.php';
300 - }
301 -
302 - $user_roles = \get_editable_roles();
303 - $roles = [];
304 -
305 - foreach ( $user_roles as $key => $role ) {
306 - if ( empty( $role['capabilities'] ) ) {
307 - continue;
308 - }
309 - $roles[ $key ] = $role['name'];
310 - }
311 -
312 - // Only prime the object cache on a miss. Re-writing it on every hit
313 - // also re-appended the key to the tracked data-cache index.
314 - wp_cache_set( $cache_key, $roles, Cache::group(), 12 * HOUR_IN_SECONDS );
315 - Cache::set_data_cache_key( $cache_key );
316 - }
317 -
318 - $memo = $roles;
319 -
320 - return $memo;
321 - }
322 -
323 66 /**
324 - * Stripslashes value.
67 + * @param $data
325 68 *
326 - * @param mixed $data Data.
327 - *
328 69 * @return mixed|string
329 70 */
330 71 public static function stripslashes_value( $data ) {
331 - if ( is_array( $data ) ) {
72 + if ( is_string( $data ) && strpos( $data, '\\' ) !== false ) {
73 + return stripslashes( $data );
74 + } elseif ( is_array( $data ) ) {
332 75 foreach ( $data as $key => $value ) {
333 76 $data[ $key ] = self::stripslashes_value( $value );
334 77 }
335 - } elseif ( is_string( $data ) ) {
336 - $trimmed = trim( $data );
337 - // Try to decode JSON.
338 - $json = json_decode( $trimmed, true );
339 - if ( json_last_error() === JSON_ERROR_NONE && is_array( $json ) ) {
340 - // Recursively stripslashes on decoded array.
341 - $json = self::stripslashes_value( $json );
342 - return wp_json_encode( $json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
343 - }
344 - // Not valid JSON, just stripslashes if needed.
345 - if ( strpos( $data, '\\' ) !== false ) {
346 - return stripslashes( $data );
347 - }
348 78 }
349 79
350 80 return $data;
351 81 }
@@ -350,12 +80,10 @@
350 80 return $data;
351 81 }
352 82
353 83 /**
354 - * Multiselect settings field value
84 + * @param $string
355 85 *
356 - * @param string $string String.
357 - *
358 86 * @return array
359 87 */
360 88 public static function multiselect_settings_field_value( $string ) {
361 89
@@ -375,10 +103,8 @@
375 103 return $values;
376 104 }
377 105
378 106 /**
379 - * Check if is block theme
380 - *
381 107 * @return bool
382 108 */
383 109 public static function check_is_block_theme(): bool {
384 110 global $wp_version;
@@ -386,19 +112,16 @@
386 112 return version_compare( $wp_version, '5.9', '>=' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
387 113 }
388 114
389 115 /**
390 - * Locate template
391 - *
392 116 * @param string $template_name Template name.
393 117 * @param string $template_path Template path. (default: '').
394 - * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin.
118 + * @param string $plugin_path Plugin path. (default: ''). fallback file from plugin
395 119 *
396 120 * @return mixed|void
397 121 */
398 122 public static function locate_template( $template_name, $template_path = '', $plugin_path = '' ) {
399 - $template_name = self::sanitize_file_name( $template_name ) . '.php';
400 -
123 + $template_name = $template_name . '.php';
401 124 if ( ! $template_path ) {
402 125 $template_path = rtsb()->get_template_path();
403 126 }
404 127 if ( ! $plugin_path ) {
@@ -420,12 +143,12 @@
420 143 )
421 144 );
422 145
423 146 if ( ! $located ) {
424 - if ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
147 + if ( file_exists( $plugin_path ) ) {
148 + return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
149 + } elseif ( $pro_plugin_path && rtsb()->has_pro() && file_exists( $pro_plugin_path ) ) {
425 150 return apply_filters( 'rtsb/core/locate_template', $pro_plugin_path, $template_name );
426 - } elseif ( file_exists( $plugin_path ) ) {
427 - return apply_filters( 'rtsb/core/locate_template', $plugin_path, $template_name );
428 151 }
429 152 }
430 153
431 154 /**
@@ -441,19 +164,8 @@
441 164 return apply_filters( 'rtsb/core/locate_template', $located, $template_name );
442 165 }
443 166
444 167 /**
445 - * Remove any character that is not alphanumeric, /, _, or -.
446 - *
447 - * @param string $name Name to sanitize.
448 - *
449 - * @return array|string|string[]|null
450 - */
451 - private static function sanitize_file_name( $name ) {
452 - return preg_replace( '/[^a-zA-Z0-9\/_\-]/', '', $name );
453 - }
454 -
455 - /**
456 168 * Template Content
457 169 *
458 170 * @param string $template_name Template name.
459 171 * @param array $args Arguments. (default: array).
@@ -462,27 +174,14 @@
462 174 * @param string $plugin_path Fallback path from where file will load if fail to load from template. (default: '').
463 175 *
464 176 * @return false|string|void
465 177 */
466 - public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
467 - $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
468 - $located = (string) wp_cache_get( $cache_key, Cache::group() );
469 - if ( ! $located ) {
470 - $located = self::locate_template( $template_name, $template_path, $plugin_path );
471 - // Don't cache the absolute path so that it can be shared between web servers with different paths.
472 - $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
473 - Cache::set_template_cache( $cache_key, $cache_path );
474 - } else {
475 - // Make sure that the absolute path to the template is resolved.
476 - $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
477 - }
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 +
478 181 if ( ! file_exists( $located ) ) {
479 - // $located is empty when the template could not be found anywhere,
480 - // so report the requested name instead of an empty path.
481 - $reported = $located ? $located : $template_name;
482 -
483 182 // translators: %s template.
484 - self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . esc_html( $reported ) . '</code>' ), '1.0' );
183 + self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
485 184
486 185 return;
487 186 }
488 187
@@ -487,9 +186,9 @@
487 186 }
488 187
489 188 if ( ! empty( $args ) && is_array( $args ) ) {
490 189 $atts = $args;
491 - extract( $args ); // @codingStandardsIgnoreLine
190 + extract( $args ); // @codingStandardsIgnoreLine
492 191 }
493 192
494 193 // Allow 3rd party plugin filter template file from their plugin.
495 194 $located = apply_filters( 'rtsb/core/get_template', $located, $template_name, $args );
@@ -499,9 +198,8 @@
499 198 }
500 199
501 200 do_action( 'rtsb/core/before_template_part', $template_name, $located, $args );
502 201 include $located;
503 -
504 202 do_action( 'rtsb/core/after_template_part', $template_name, $located, $args );
505 203
506 204 if ( $return ) {
507 205 return ob_get_clean();
@@ -604,9 +302,9 @@
604 302 // Return the Builder Template id.
605 303
606 304 if ( get_post_type( get_the_ID() ) == BuilderFns::$post_type_tb ) {
607 305 $product_id = get_post_meta( get_the_ID(), BuilderFns::$product_template_meta, true );
608 - if ( $product_id && 'product' === get_post_type( $product_id ) && get_post_status( $product_id ) ) {
306 + if ( $product_id ) {
609 307 return $product_id;
610 308 }
611 309 }
612 310
@@ -611,16 +309,15 @@
611 309 }
612 310
613 311 global $wpdb;
614 312 $cache_key = 'rtsb_prepared_product_id';
615 - $_post_id = wp_cache_get( $cache_key, Cache::group() );
313 + $_post_id = wp_cache_get( $cache_key );
314 +
616 315 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
617 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
618 316 $_post_id = $wpdb->get_var(
619 - $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
317 + $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status = %s", 'product', 'publish' )
620 318 );
621 - wp_cache_set( $cache_key, $_post_id, Cache::group(), 12 * HOUR_IN_SECONDS );
622 - Cache::set_data_cache_key( $cache_key );
319 + wp_cache_set( $cache_key, $_post_id, '', 12 * HOUR_IN_SECONDS );
623 320 }
624 321
625 322 return $_post_id;
626 323 }
@@ -625,26 +322,19 @@
625 322 return $_post_id;
626 323 }
627 324
628 325 /**
629 - * Get the product function. Only used in single page widgets.
326 + * Get the product function.
630 327 *
631 328 * @return object
632 329 */
633 330 public static function get_product() {
634 - global $product;
635 -
331 + global $post, $product;
636 332 if ( is_singular( 'product' ) && $product instanceof WC_Product ) {
637 333 return $product;
638 334 }
639 - $cache_key = 'prepared_product_for_preview';
640 - if ( isset( self::$cache[ $cache_key ] ) ) {
641 - return self::$cache[ $cache_key ];
642 - }
643 - $product = wc_get_product( self::get_prepared_product_id() );
644 - self::$cache[ $cache_key ] = $product;
645 - do_action( 'rtsb_before_product_template_render' );
646 - return $product;
335 +
336 + return wc_get_product( self::get_prepared_product_id() );
647 337 }
648 338
649 339 /**
650 340 * Error function
@@ -655,11 +345,10 @@
655 345 *
656 346 * @return void
657 347 */
658 348 public static function doing_it_wrong( $function, $message, $version ) {
659 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary
660 349 $message .= ' Backtrace: ' . wp_debug_backtrace_summary();
661 - _doing_it_wrong( esc_html( $function ), wp_kses_post( $message ), esc_html( $version ) );
350 + _doing_it_wrong( $function, $message, $version );
662 351 }
663 352
664 353 /**
665 354 * @return array
@@ -664,17 +353,8 @@
664 353 /**
665 354 * @return array
666 355 */
667 356 public static function get_pages() {
668 - // Memoized because this only feeds admin select fields, yet the settings
669 - // schema is built on every request and asks for it several times. Each
670 - // miss runs a full get_pages() query and hydrates every page object.
671 - static $memo = null;
672 -
673 - if ( null !== $memo ) {
674 - return $memo;
675 - }
676 -
677 357 $pages = [];
678 358 $rawPages = get_pages();
679 359 if ( ! empty( $rawPages ) ) {
680 360 foreach ( $rawPages as $page ) {
@@ -681,20 +361,11 @@
681 361 $pages[ $page->ID ] = $page->post_title;
682 362 }
683 363 }
684 364
685 - $memo = $pages;
686 -
687 - return $memo;
365 + return $pages;
688 366 }
689 367
690 - /**
691 - * Get section items
692 - *
693 - * @param string $section_id Section ID.
694 - *
695 - * @return array
696 - */
697 368 public static function get_section_items( $section_id ) {
698 369 if ( ! $section_id ) {
699 370 return [];
700 371 }
@@ -701,16 +372,8 @@
701 372
702 373 return DataModel::source()->get_option( $section_id, [] );
703 374 }
704 375
705 - /**
706 - * Get options items
707 - *
708 - * @param string $section_id Section ID.
709 - * @param string $item_id Item ID.
710 - *
711 - * @return array
712 - */
713 376 public static function get_options( $section_id, $item_id ) {
714 377 if ( ! $section_id || ! $item_id ) {
715 378 return [];
716 379 }
@@ -721,11 +384,11 @@
721 384
722 385 /**
723 386 * Get options value with default value if options doesn't exist.
724 387 *
725 - * @param array $group Group.
726 - * @param string $option_key Option key.
727 - * @param string $default_value Default value.
388 + * @param $group
389 + * @param $option_key
390 + * @param $default_value
728 391 *
729 392 * @return mixed|string
730 393 */
731 394 public static function get_options_by_default_val( $group, $option_key, $default_value = '' ) {
@@ -737,34 +400,32 @@
737 400 return $group[ $option_key ];
738 401 }
739 402
740 403 /**
741 - * Get option.
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
742 409 *
743 - * @param string $section_id Section ID.
744 - * @param string $item_id Item ID.
745 - * @param string $option_id Option ID.
746 - * @param null $default EXCEPT multi_checkbox you can provide default value if given option does not set any value.
747 - * @param null $type checkbox, multi_checkbox, number.
748 - *
749 410 * @return bool|int|mixed|null
750 411 */
751 412 public static function get_option( $section_id, $item_id, $option_id, $default = null, $type = null ) {
752 413 $options = self::get_options( $section_id, $item_id );
753 414
754 - if ( 'checkbox' === $type ) {
415 + if ( $type === 'checkbox' ) {
755 416 if ( isset( $options[ $option_id ] ) ) {
756 - return 'on' === $options[ $option_id ];
417 + return $options[ $option_id ] === 'on';
757 418 }
758 419
759 420 return $default;
760 - } elseif ( 'multi_checkbox' === $type ) {
761 - return isset( $options[ $option_id ] ) && is_array( $options[ $option_id ] ) && in_array( $default, $options[ $option_id ] ); // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
762 - } elseif ( 'number' === $type ) {
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' ) {
763 424 return isset( $options[ $option_id ] ) ? absint( $options[ $option_id ] ) : absint( $default );
764 425 }
765 426
766 - return ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
427 + return isset( $options[ $option_id ] ) && ! empty( $options[ $option_id ] ) ? $options[ $option_id ] : $default;
767 428 }
768 429
769 430
770 431 /**
@@ -818,14 +479,13 @@
818 479 }
819 480
820 481 if ( strlen( $page_content ) > 0 ) {
821 482 // Search for an existing page with the specified page content (typically a shortcode).
822 - $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
823 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
483 + $shortcode = str_replace( [ '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ], '', $page_content );
824 484 $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}%" ) );
825 485 } else {
826 486 // Search for an existing page with the specified page slug.
827 - $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
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 ) );
828 488 }
829 489
830 490 $valid_page_found = apply_filters( 'rtsb/core/create_page_id', $valid_page_found, $slug, $page_content, $options );
831 491
@@ -847,12 +507,12 @@
847 507
848 508 // Search for a matching valid trashed page.
849 509 if ( strlen( $page_content ) > 0 ) {
850 510 // Search for an existing page with the specified page content (typically a shortcode).
851 - $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
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}%" ) );
852 512 } else {
853 513 // Search for an existing page with the specified page slug.
854 - $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
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 ) );
855 515 }
856 516
857 517 if ( $trashed_page_found ) {
858 518 $page_id = $trashed_page_found;
@@ -891,13 +551,8 @@
891 551
892 552 return $page_id;
893 553 }
894 554
895 - /**
896 - * Get allowed HTML tags.
897 - *
898 - * @return array
899 - */
900 555 public static function get_kses_array() {
901 556 return [
902 557 'a' => [
903 558 'class' => [],
@@ -996,14 +651,13 @@
996 651 ];
997 652 }
998 653
999 654
1000 - /**
655 + /*
1001 656 * Escape output of wishlist icon
1002 657 *
1003 658 * @param string $data Data to escape.
1004 - *
1005 - * @return void
659 + * @return string Escaped data
1006 660 */
1007 661 public static function print_icon( $data ) {
1008 662 /**
1009 663 * APPLY_FILTERS: rtsb/core/allowed_icon_html
@@ -1062,16 +716,8 @@
1062 716
1063 717 /**
1064 718 * Prints HTMl.
1065 719 *
1066 - * The full-markup branch is echoed as-is. It used to run stripslashes_deep(),
1067 - * which corrupts already-rendered markup: product loop templates embed JSON in
1068 - * attributes (e.g. the Variation Swatches `data-product_variations` payload),
1069 - * and stripping backslashes turns `\"` into `"` and `\n` into `n`, so the JSON
1070 - * no longer parses and every swatch renders disabled. Rendered HTML never
1071 - * carries WordPress slashes — values that do are unslashed where they are read
1072 - * (see stripslashes_value()), not at output time.
1073 - *
1074 720 * @param string $html HTML.
1075 721 * @param bool $allHtml All HTML.
1076 722 *
1077 723 * @return void
@@ -1080,9 +726,9 @@
1080 726 if ( ! $html ) {
1081 727 return;
1082 728 }
1083 729 if ( $allHtml ) {
1084 - echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
730 + echo stripslashes_deep( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1085 731 } else {
1086 732 echo wp_kses_post( stripslashes_deep( $html ) );
1087 733 }
1088 734 }
@@ -1095,9 +741,9 @@
1095 741 * @return mixed
1096 742 */
1097 743 public static function allowedHtml( $level = 'basic' ) {
1098 744 $allowed_html = [];
1099 -
745 + // TODO:: Need Optimize.
1100 746 switch ( $level ) {
1101 747 case 'basic':
1102 748 $allowed_html = [
1103 749 'b' => [
@@ -1237,51 +883,8 @@
1237 883 return $allowed_html;
1238 884 }
1239 885
1240 886 /**
1241 - * Safe get a validated HTML tag.
1242 - *
1243 - * @param string $tag HTML tag.
1244 - *
1245 - * @return string
1246 - */
1247 - public static function get_validated_html_tag( $tag ) {
1248 - $allowed_html_wrapper_tags = [
1249 - 'a',
1250 - 'article',
1251 - 'aside',
1252 - 'button',
1253 - 'div',
1254 - 'footer',
1255 - 'h1',
1256 - 'h2',
1257 - 'h3',
1258 - 'h4',
1259 - 'h5',
1260 - 'h6',
1261 - 'header',
1262 - 'main',
1263 - 'nav',
1264 - 'p',
1265 - 'section',
1266 - 'span',
1267 - ];
1268 -
1269 - return in_array( strtolower( $tag ), $allowed_html_wrapper_tags, true ) ? $tag : 'div';
1270 - }
1271 -
1272 - /**
1273 - * Safe print a validated HTML tag.
1274 - *
1275 - * @param string $tag HTML tag.
1276 - *
1277 - * @return void
1278 - */
1279 - public static function print_validated_html_tag( $tag ) {
1280 - self::print_html( self::get_validated_html_tag( $tag ) );
1281 - }
1282 -
1283 - /**
1284 887 * Insert Some array element
1285 888 *
1286 889 * @param [type] $key The elements will insert nearby this key.
1287 890 * @param [type] $main_array Original array.
@@ -1311,18 +914,8 @@
1311 914 *
1312 915 * @return array
1313 916 */
1314 917 public static function get_image_sizes() {
1315 - // Memoized: called repeatedly while the settings schema is built, and each
1316 - // call loops every registered size doing three get_option() lookups for
1317 - // the core ones. Image sizes are registered on after_setup_theme, long
1318 - // before anything here runs, so the list cannot change mid-request.
1319 - static $memo = null;
1320 -
1321 - if ( null !== $memo ) {
1322 - return $memo;
1323 - }
1324 -
1325 918 global $_wp_additional_image_sizes;
1326 919
1327 920 $sizes = [];
1328 921
@@ -1342,31 +935,23 @@
1342 935
1343 936 $imgSize = [];
1344 937
1345 938 foreach ( $sizes as $key => $img ) {
1346 - $imgSize[ $key ] = esc_html( $key ) . " ({$img['width']}*{$img['height']})";
939 + $imgSize[ $key ] = ucfirst( $key ) . " ({$img['width']}*{$img['height']})";
1347 940 }
1348 941
1349 942 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1350 943 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
1351 944
1352 - $memo = $imgSize;
1353 -
1354 - return $memo;
945 + return $imgSize;
1355 946 }
1356 947
1357 948 /**
1358 949 * Free Layouts
1359 950 *
1360 - * @param string $layout Layout.
1361 - *
1362 951 * @return array
1363 952 */
1364 - public static function free_layouts( $layout = '' ) {
1365 - if ( ! empty( $layout ) && false !== strpos( $layout, 'rtsb-' ) ) {
1366 - return [ $layout => esc_html__( 'Addon Layout', 'shopbuilder' ) ];
1367 - }
1368 -
953 + public static function free_layouts() {
1369 954 $layouts = [
1370 955 'grid-layout1' => esc_html__( 'Grid Layout 1', 'shopbuilder' ),
1371 956 'grid-layout2' => esc_html__( 'Grid Layout 2', 'shopbuilder' ),
1372 957 'list-layout1' => esc_html__( 'List Layout 1', 'shopbuilder' ),
@@ -1375,8 +960,9 @@
1375 960 'slider-layout2' => esc_html__( 'Slider Layout 2', 'shopbuilder' ),
1376 961 'category-single-layout1' => esc_html__( 'Category Single Layout 1', 'shopbuilder' ),
1377 962 'category-layout1' => esc_html__( 'Category Layout 1', 'shopbuilder' ),
1378 963 'category-layout2' => esc_html__( 'Category Layout 2', 'shopbuilder' ),
964 + 'category-layout3' => esc_html__( 'Category Layout 2', 'shopbuilder' ),
1379 965 ];
1380 966
1381 967 return apply_filters( 'rtsb/elements/elementor/free_layouts', $layouts );
1382 968 }
@@ -1394,19 +980,10 @@
1394 980
1395 981 if ( empty( $taxonomy ) ) {
1396 982 return $terms;
1397 983 }
1398 - $cache_key = 'rtsb_get_all_terms_by_tax_name_' . $taxonomy;
1399 - if ( isset( self::$cache[ $cache_key ] ) ) {
1400 - return self::$cache[ $cache_key ];
1401 - }
1402 984
1403 - $termList = get_terms(
1404 - [
1405 - 'taxonomy' => $taxonomy,
1406 - 'hide_empty' => false,
1407 - ]
1408 - );
985 + $termList = get_terms( [ $taxonomy ], [ 'hide_empty' => 0 ] );
1409 986
1410 987 if ( is_array( $termList ) && ! empty( $termList ) && empty( $termList['errors'] ) ) {
1411 988 if ( $placeholder ) {
1412 989 $terms = [
@@ -1417,28 +994,11 @@
1417 994 foreach ( $termList as $term ) {
1418 995 $terms[ $term->term_id ] = esc_html( $term->name );
1419 996 }
1420 997 }
1421 - self::$cache[ $cache_key ] = $terms;
998 +
1422 999 return $terms;
1423 1000 }
1424 - /**
1425 - * Get all product attribute taxonomy by id
1426 - *
1427 - * @param array $term_ids Term id.
1428 - *
1429 - * @return array
1430 - */
1431 - public static function tax_filter_attr_taxonomy( $term_ids ) {
1432 - $taxonomies = [];
1433 - foreach ( $term_ids as $id ) {
1434 - $term = get_term( $id );
1435 - if ( ! is_wp_error( $term ) && $term ) {
1436 - $taxonomies[] = $term->taxonomy;
1437 - }
1438 - }
1439 - return array_unique( $taxonomies );
1440 - }
1441 1001
1442 1002 /**
1443 1003 * Get all terms by attributes
1444 1004 *
@@ -1444,17 +1004,8 @@
1444 1004 *
1445 1005 * @return array
1446 1006 */
1447 1007 public static function get_all_attributes() {
1448 - // Memoize per request: this runs once per product widget while Elementor
1449 - // builds the editor control config, firing a get_terms() query per
1450 - // attribute taxonomy each time. Caching avoids the repeated N queries.
1451 - static $cached = null;
1452 -
1453 - if ( null !== $cached ) {
1454 - return $cached;
1455 - }
1456 -
1457 1008 $terms = [];
1458 1009 $termList = [];
1459 1010
1460 1011 $attributes = wc_get_attribute_taxonomies();
@@ -1461,9 +1012,9 @@
1461 1012
1462 1013 if ( $attributes ) {
1463 1014 foreach ( $attributes as $tax ) {
1464 1015 if ( taxonomy_exists( wc_attribute_taxonomy_name( $tax->attribute_name ) ) ) {
1465 - $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ) );
1016 + $termList[ $tax->attribute_name ] = get_terms( wc_attribute_taxonomy_name( $tax->attribute_name ), 'orderby=name&hide_empty=0' );
1466 1017 }
1467 1018 }
1468 1019 }
1469 1020
@@ -1474,10 +1025,8 @@
1474 1025 }
1475 1026 }
1476 1027 }
1477 1028
1478 - $cached = $terms;
1479 -
1480 1029 return $terms;
1481 1030 }
1482 1031
1483 1032 /**
@@ -1557,12 +1106,9 @@
1557 1106 *
1558 1107 * @return int|array
1559 1108 */
1560 1109 public static function get_terms( $taxonomy, $first_term = false, $only_parents = false, $return_slug = false ) {
1561 - if ( ! is_admin() ) {
1562 - return [];
1563 - }
1564 -
1110 + // TODO:: Return Slug always true for all settings.
1565 1111 $term_list = [];
1566 1112 $args = [
1567 1113 'taxonomy' => $taxonomy,
1568 1114 'hide_empty' => false,
@@ -1595,67 +1141,25 @@
1595 1141
1596 1142 /**
1597 1143 * Get product rating.
1598 1144 *
1599 - * @param array $args Arguments.
1600 - *
1601 1145 * @return string|void
1602 1146 */
1603 - public static function get_product_rating_html( $args = [] ) {
1147 + public static function get_product_rating_html() {
1604 1148 global $product;
1605 1149
1606 - $html = '';
1607 - $rating_count = $product->get_rating_count();
1608 - $average_rating = $product->get_average_rating();
1150 + $html = '';
1151 + $rating_count = $product->get_rating_count();
1152 + $average = $product->get_average_rating();
1609 1153
1610 - if ( ! rtsb()->has_pro() || empty( $args ) ) {
1611 - if ( ! $rating_count || empty( wc_get_rating_html( $average_rating, $rating_count ) ) ) {
1612 - return $html;
1613 - }
1614 -
1615 - $html .= '<div class="product-rating">';
1616 - $html .= wc_get_rating_html( $average_rating, $rating_count );
1617 - $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1618 - $html .= '</div>';
1619 -
1620 - self::print_html( $html, true );
1621 -
1622 - return;
1154 + if ( ! $rating_count || empty( wc_get_rating_html( $average, $rating_count ) ) ) {
1155 + return $html;
1623 1156 }
1624 1157
1625 - if ( empty( $rating_count ) && ! $args['show_empty_rating'] ) {
1626 - return '';
1627 - }
1628 -
1629 - $preset = ! empty( $args['preset'] ) ? $args['preset'] : 'preset1';
1630 - $average = $args['show_average_rating'] ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1631 - $count = '';
1632 -
1633 - if ( $args['show_rating_count'] ) {
1634 - $count .= '<div class="rtsb-count">';
1635 - $count .= sprintf(
1636 - /* translators: %s is the number of reviews */
1637 - _n( '%s Review', '%s Reviews', $rating_count, 'shopbuilder' ),
1638 - $rating_count
1639 - );
1640 - $count .= '</div>';
1641 - }
1642 -
1643 - if ( 'preset2' === $preset ) {
1644 - $average = $args['show_average_rating'] ? '<div class="inner-wrapper"><span class="star-icon"></span><span class="average-rating">' . $average_rating . '</span></div>' : '';
1645 - }
1646 -
1647 - $html .= '<div class="product-rating ' . esc_attr( $preset ) . '">';
1648 -
1649 - if ( 'preset1' === $preset ) {
1650 - $html .= wc_get_rating_html( $average_rating, $rating_count );
1651 - $html .= $average;
1652 - $html .= $count;
1653 - } elseif ( 'preset2' === $preset ) {
1654 - $html .= $average;
1655 - $html .= $count;
1656 - }
1657 -
1158 + $html .= '<div class="product-rating">';
1159 + $html .= wc_get_rating_html( $average, $rating_count );
1160 + // TODO:: Disable Rating Count option need to apply.
1161 + $html .= ! empty( $html ) ? '<span class="count">(' . $average . ')</span>' : '';
1658 1162 $html .= '</div>';
1659 1163
1660 1164 self::print_html( $html, true );
1661 1165 }
@@ -1660,28 +1164,8 @@
1660 1164 self::print_html( $html, true );
1661 1165 }
1662 1166
1663 1167 /**
1664 - * Get product simple rating.
1665 - *
1666 - * @return void
1667 - */
1668 - public static function get_product_simple_rating_html() {
1669 - global $product;
1670 - $html = null;
1671 - $rating_count = $product->get_rating_count();
1672 - $average_rating = $product->get_average_rating();
1673 - $html .= '<div class="product-rating">';
1674 - $html .= wc_get_rating_html( $average_rating, $rating_count );
1675 - $html .= ! empty( $html ) ? '<span class="rtsb-count">(' . $average_rating . ')</span>' : '';
1676 - $html .= '</div>';
1677 -
1678 - self::print_html( $html, true );
1679 - }
1680 -
1681 -
1682 -
1683 - /**
1684 1168 * Text truncation.
1685 1169 *
1686 1170 * @param string $text_to_truncate Text.
1687 1171 * @param int $limit Limit.
@@ -1723,14 +1207,8 @@
1723 1207 *
1724 1208 * @return void
1725 1209 */
1726 1210 public static function get_badge_html( $text, $class = 'fill' ) {
1727 - if ( self::is_module_active( 'product_badges' ) && 'rtsb_yes' === $text ) {
1728 - do_action( 'rtsb/modules/product_badges/frontend/display' );
1729 -
1730 - return;
1731 - }
1732 -
1733 1211 if ( empty( $text ) ) {
1734 1212 return;
1735 1213 }
1736 1214 ob_start();
@@ -1737,9 +1215,10 @@
1737 1215 ?>
1738 1216
1739 1217 <ul class="rtsb-promotion-list">
1740 1218 <li class="rtsb-promotion-list-item">
1741 - <span class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1219 + <span
1220 + class="rtsb-tag-<?php echo ! empty( $class ) ? esc_attr( $class ) : ''; ?>"><?php echo esc_html( $text ); ?></span>
1742 1221 </li>
1743 1222 </ul>
1744 1223
1745 1224 <?php
@@ -1777,59 +1256,25 @@
1777 1256
1778 1257 <?php
1779 1258 self::print_html( ob_get_clean() );
1780 1259 }
1781 - /**
1782 - * Brands HTML
1783 - *
1784 - * @param int $id Product ID.
1785 - * @param string $class Custom class.
1786 - *
1787 - * @return void|string
1788 - */
1789 - public static function get_brands_list( $id, $class = 'rtsb-brand-outline' ) {
1790 - $terms = get_the_terms( $id, 'product_brand' );
1791 - if ( empty( $id ) || empty( $terms ) || is_wp_error( $terms ) ) {
1792 - return '';
1793 - }
1794 - ob_start();
1795 - ?>
1796 1260
1797 - <ul class="rtsb-brand-list <?php echo esc_attr( $class ); ?>">
1798 - <?php
1799 - $brand_list = get_the_term_list(
1800 - $id,
1801 - 'product_brand',
1802 - '<li class="rtsb-brand-list-item">',
1803 - '</li><li class="rtsb-brand-list-item">',
1804 - '</li>'
1805 - );
1806 1261
1807 - self::print_html( $brand_list );
1808 - ?>
1809 - </ul>
1810 -
1811 - <?php
1812 - self::print_html( ob_get_clean() );
1813 - }
1814 -
1815 1262 /**
1816 1263 * Get Featured Image HTML.
1817 1264 *
1818 - * @param string $type Image type.
1819 - * @param int $post_id Post ID.
1820 - * @param string $f_img_size Image size.
1821 - * @param null $default_img_id Default image ID.
1822 - * @param array $custom_img_size Custom image size.
1823 - * @param bool $lazy Lazy load check.
1824 - * @param bool $hover Hover image.
1825 - * @param bool $gallery Gallery image.
1826 - * @param bool $custom_image_id Custom category image ID.
1827 - * @param bool|string $priority Eager/priority hint: 'high', 'eager', or false (default lazy).
1265 + * @param string $type Image type.
1266 + * @param int $post_id Post ID.
1267 + * @param string $f_img_size Image size.
1268 + * @param null $default_img_id Default image ID.
1269 + * @param array $custom_img_size Custom image size.
1270 + * @param bool $lazy Lazy load check.
1271 + * @param bool $hover Hover image.
1272 + * @param bool $gallery Gallery image.
1828 1273 *
1829 1274 * @return string|null
1830 1275 */
1831 - 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, $priority = false ) {
1276 + 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 ) {
1832 1277 $img_html = null;
1833 1278 $attachment_id = null;
1834 1279 $c_size = false;
1835 1280 $hover_class = '';
@@ -1853,9 +1298,9 @@
1853 1298 if ( 'product' === $type ) {
1854 1299 $a_id = get_post_thumbnail_id( $post_id );
1855 1300 $post_title = get_the_title( $post_id );
1856 1301 } elseif ( 'category' === $type ) {
1857 - $a_id = $custom_image_id ? $custom_image_id : get_term_meta( $post_id, 'thumbnail_id', true );
1302 + $a_id = get_term_meta( $post_id, 'thumbnail_id', true );
1858 1303 $post_title = get_term( $post_id )->name;
1859 1304 } else {
1860 1305 $a_id = $default_img_id;
1861 1306 }
@@ -1868,19 +1313,8 @@
1868 1313 'class' => 'img-responsive rtsb-product-image' . $lazy_class . $hover_class,
1869 1314 'alt' => $alt_tag,
1870 1315 ];
1871 1316
1872 - // Eager-load above-the-fold images so the LCP candidate is fetched
1873 - // immediately instead of being lazy-loaded after render-blocking CSS.
1874 - // Only the first image is marked fetchpriority=high to avoid competing hints.
1875 - if ( $priority ) {
1876 - $attr['loading'] = 'eager';
1877 -
1878 - if ( 'high' === $priority ) {
1879 - $attr['fetchpriority'] = 'high';
1880 - }
1881 - }
1882 -
1883 1317 if ( $a_id ) {
1884 1318 $img_html = wp_get_attachment_image( $a_id, $f_img_size, false, $attr );
1885 1319 $attachment_id = $a_id;
1886 1320 }
@@ -1927,9 +1361,9 @@
1927 1361
1928 1362 if ( ! $img_html ) {
1929 1363 $hwstring = image_hwstring( 160, 160 );
1930 1364 $attr = isset( $attr['src'] ) ? apply_filters( 'wp_get_attachment_image_attributes', $attr, false, $f_img_size ) : [];
1931 - $attr['class'] = 'default-img ' . $hover_class;
1365 + $attr['class'] = 'default-img';
1932 1366 $attr['src'] = esc_url( rtsb()->get_assets_uri( 'images/demo.png' ) );
1933 1367 $attr['alt'] = esc_html__( 'Default Image', 'shopbuilder' );
1934 1368 $img_html = rtrim( "<img $hwstring" );
1935 1369
@@ -2016,20 +1450,8 @@
2016 1450 *
2017 1451 * @return string
2018 1452 */
2019 1453 public static function icons_manager( $control, $class = '', $builder = 'elementor' ): string {
2020 - if ( empty( $control['value'] ) ) {
2021 - return '';
2022 - }
2023 - if ( is_array( $control['value'] ) ) {
2024 - $cache_key = 'icons_managet_' . str_replace( ' ', '', md5( serialize( $control['value'] ) ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
2025 - } else {
2026 - $cache_key = 'icons_managet_' . str_replace( ' ', '', $control['value'] );
2027 - }
2028 - if ( isset( self::$cache[ $cache_key ] ) ) {
2029 - return self::$cache[ $cache_key ];
2030 - }
2031 -
2032 1454 $attributes = [
2033 1455 'aria-hidden' => 'true',
2034 1456 ];
2035 1457
@@ -2042,11 +1464,9 @@
2042 1464 if ( defined( 'ELEMENTOR_VERSION' ) && ! empty( $control ) && 'elementor' === $builder ) {
2043 1465 Icons_Manager::render_icon( $control, $attributes );
2044 1466 }
2045 1467
2046 - $icons = ob_get_clean();
2047 - self::$cache[ $cache_key ] = $icons;
2048 - return $icons;
1468 + return ob_get_clean();
2049 1469 }
2050 1470
2051 1471 /**
2052 1472 * Get product swatches.
@@ -2058,25 +1478,9 @@
2058 1478 public static function get_product_swatches( $type ) {
2059 1479 ?>
2060 1480 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
2061 1481 <?php
2062 - /*
2063 - * Hand off to the Variation Swatches plugin's own archive renderer when it
2064 - * is listening, otherwise fall back to the built-in module.
2065 - *
2066 - * Detected with has_action() rather than a class name on purpose: this used
2067 - * to test `class_exists( 'Rtwpvsp' )`, but the plugin moved its classes into
2068 - * a namespace (`Rtwpvsp\Rtwpvsp`), so that check silently started failing.
2069 - * The else branch then fired the built-in module's action — which is never
2070 - * hooked while the plugin is active, because the module deliberately stands
2071 - * down — leaving this wrapper rendered but empty. Asking whether anything is
2072 - * actually listening is both the correct question and rename-proof.
2073 - */
2074 - if ( has_action( 'rtwpvs_show_archive_variation' ) ) {
2075 - do_action( 'rtwpvs_show_archive_variation' );
2076 - } else {
2077 - do_action( 'rtsb/vs/showcase/variation' );
2078 - }
1482 + do_action( 'rtwpvs_show_archive_variation' );
2079 1483 ?>
2080 1484 </div>
2081 1485 <?php
2082 1486 }
@@ -2100,143 +1504,68 @@
2100 1504 if ( ! $product->is_on_sale() ) {
2101 1505 return '';
2102 1506 }
2103 1507
2104 - if ( 'text' === $type ) {
2105 - return $text;
2106 - }
1508 + $badge_text = '';
1509 + $percentage = null;
1510 + $max_percentage = '';
2107 1511
2108 - $percentage = self::calculate_sale_percentage( $product );
1512 + if ( $product->is_type( 'simple' ) ) {
1513 + $max_percentage = ( ( $product->get_regular_price() - $product->get_sale_price() ) / $product->get_regular_price() ) * 100;
1514 + } elseif ( $product->is_type( 'variable' ) ) {
1515 + $max_percentage = 0;
2109 1516
2110 - // The product is on sale (checked above). When a percentage can be derived show
2111 - // it; otherwise (e.g. some variable products where variation prices aren't
2112 - // primed) fall back to the sale text so an on-sale product always shows a badge.
2113 - return $percentage > 0 ? '-' . round( $percentage ) . '%' : $text;
2114 - }
1517 + foreach ( $product->get_children() as $child_id ) {
2115 1518
2116 - /**
2117 - * Get sale badge
2118 - *
2119 - * @param object $product Product Object.
2120 - * @param string $type Badge type.
2121 - * @param string $text Badge text.
2122 - * @param string $out_of_stock_text Out of stock text.
2123 - *
2124 - * @return string
2125 - */
2126 - public static function get_promo_badge( $product, $type, $text, $out_of_stock_text ) {
2127 - $disable_badges = self::get_option( 'general', 'guest_user', 'hide_badges', '' );
2128 - $badges_visibility = rtsb()->has_pro() && ! is_user_logged_in() && $disable_badges;
1519 + $variation = wc_get_product( $child_id );
2129 1520
2130 - if ( $badges_visibility ) {
2131 - return '';
2132 - }
1521 + $price = $variation->get_regular_price();
1522 + $sale = $variation->get_sale_price();
2133 1523
2134 - if ( is_null( $product ) ) {
2135 - global $product;
2136 - }
1524 + if ( 0 !== $price && ! empty( $sale ) ) {
1525 + $percentage = ( $price - $sale ) / $price * 100;
1526 + }
2137 1527
2138 - if ( ! $product instanceof WC_Product ) {
2139 - return '';
1528 + if ( $percentage > $max_percentage ) {
1529 + $max_percentage = $percentage;
1530 + }
1531 + }
2140 1532 }
2141 1533
2142 - if ( ! $product->is_in_stock() ) {
2143 - return $out_of_stock_text;
1534 + if ( $max_percentage > 0 ) {
1535 + $badge_text = '-' . round( $max_percentage ) . '%';
2144 1536 }
2145 1537
2146 - if ( ! $product->is_on_sale() ) {
2147 - return '';
2148 - }
2149 -
2150 1538 if ( 'text' === $type ) {
2151 - return $text;
1539 + $badge_text = $text;
2152 1540 }
2153 1541
2154 - $percentage = self::calculate_sale_percentage( $product );
2155 -
2156 - // The product is on sale (checked above). When a percentage can be derived show
2157 - // it; otherwise (e.g. some variable products where variation prices aren't
2158 - // primed) fall back to the sale text so an on-sale product always shows a badge.
2159 - return $percentage > 0 ? '-' . round( $percentage ) . '%' : $text;
1542 + return $badge_text;
2160 1543 }
2161 1544
2162 1545 /**
2163 - * Calculate Sale Percentage
2164 - *
2165 - * @param object $product Product object.
2166 - *
2167 - * @return float|int|string
2168 - */
2169 - public static function calculate_sale_percentage( $product = null ) {
2170 - if ( is_null( $product ) ) {
2171 - global $product;
2172 - }
2173 - if ( ! $product instanceof WC_Product ) {
2174 - return '';
2175 - }
2176 - if ( ! $product->is_on_sale() ) {
2177 - return '';
2178 - }
2179 - $max_percentage = 0;
2180 - if ( $product->is_type( 'simple' ) ) {
2181 - $regular_price = (float) $product->get_regular_price();
2182 - $sale_price = (float) $product->get_sale_price();
2183 - if ( $regular_price > 0 && $sale_price > 0 ) {
2184 - $max_percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2185 - }
2186 - } elseif ( $product->is_type( 'variable' ) ) {
2187 - $prices = $product->get_variation_prices( true );
2188 - if ( ! empty( $prices['regular_price'] ) && ! empty( $prices['sale_price'] ) ) {
2189 - foreach ( $prices['regular_price'] as $key => $regular_price ) {
2190 - $sale_price = $prices['sale_price'][ $key ];
2191 -
2192 - if ( $regular_price > 0 && $sale_price > 0 && $regular_price > $sale_price ) {
2193 - $percentage = ( ( $regular_price - $sale_price ) / $regular_price ) * 100;
2194 - $max_percentage = max( $max_percentage, $percentage );
2195 - }
2196 - }
2197 - }
2198 - }
2199 - return $max_percentage > 0 ? round( $max_percentage ) : 0;
2200 - }
2201 -
2202 - /**
2203 1546 * Pagination
2204 1547 *
2205 - * @param string $pages Pages.
2206 - * @param integer $per_page Per-page count (unused; kept for positional compatibility).
2207 - * @param boolean $ajax Ajax.
2208 - * @param int|null $visible_range Page links to show on each side of the current
2209 - * page (from the widget's "Page Number Range"
2210 - * control). Falls back to the filter default.
1548 + * @param string $pages Pages.
1549 + * @param integer $range Range.
1550 + * @param boolean $ajax Ajax.
2211 1551 *
2212 1552 * @return string
2213 1553 */
2214 - public static function custom_pagination( $pages = '', $per_page = 4, $ajax = false, $visible_range = null ) {
2215 - $html = null;
1554 + public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
1555 + $html = null;
1556 + $showitems = ( $range * 2 ) + 1;
2216 1557
2217 - // How many page links to show on each side of the current page. Driven by
2218 - // the widget's "Page Number Range" control; the filter is the fallback
2219 - // default. Keeping it small truncates long paginations (« ‹ 1 2 3 › »)
2220 - // with jump-to-first/last arrows instead of listing every page number.
2221 - // (The 2nd argument callers pass is the per-page count, not a window).
2222 - if ( null === $visible_range ) {
2223 - $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', 5 );
2224 - }
2225 -
2226 - $visible_range = max( 1, (int) $visible_range );
2227 -
2228 1558 global $paged;
2229 1559
2230 - // Prefer `paged` (archive pagination), fall back to `page` (paginated singular posts).
2231 - // This handles the case where the shop/archive is set as the front page — `is_front_page()`
2232 - // alone would force `page` and miss the archive `paged` value.
2233 - $paged = get_query_var( 'paged' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2234 - if ( ! $paged ) {
2235 - $paged = get_query_var( 'page' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1560 + if ( is_front_page() ) {
1561 + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1;
1562 + } else {
1563 + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
2236 1564 }
2237 - if ( ! $paged ) {
2238 - $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1565 +
1566 + if ( empty( $paged ) ) {
1567 + $paged = 1;
2239 1568 }
2240 1569
2241 1570 if ( '' === $pages ) {
2242 1571 global $wp_query;
@@ -2253,9 +1582,8 @@
2253 1582
2254 1583 if ( $ajax ) {
2255 1584 $ajaxClass = ' rtsb-pagination-ajax';
2256 1585 $dataAttr = "data-paged='1'";
2257 - $dataAttr .= ' data-visible-range="' . esc_attr( $visible_range ) . '"';
2258 1586 }
2259 1587
2260 1588 if ( 1 !== $pages ) {
2261 1589 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
@@ -2260,29 +1588,29 @@
2260 1588 if ( 1 !== $pages ) {
2261 1589 $html .= '<div class="rtsb-pagination' . $ajaxClass . '" ' . $dataAttr . '>';
2262 1590 $html .= '<ul class="pagination-list">';
2263 1591
2264 - if ( $paged > 2 && $paged > $visible_range + 1 && $visible_range < $pages ) {
1592 + if ( $paged > 2 && $paged > $range + 1 && $showitems < $pages ) {
2265 1593 $html .= "<li><a data-paged='1' href='" . get_pagenum_link( 1 ) . "' aria-label='First'>&laquo;</a></li>";
2266 1594 }
2267 1595
2268 - if ( $paged > 1 && $visible_range < $pages ) {
1596 + if ( $paged > 1 && $showitems < $pages ) {
2269 1597 $p = $paged - 1;
2270 1598 $html .= "<li><a data-paged='{$p}' href='" . get_pagenum_link( $p ) . "' aria-label='Previous'>&lsaquo;</a></li>";
2271 1599 }
2272 1600
2273 1601 for ( $i = 1; $i <= $pages; $i++ ) {
2274 - if ( 1 != $pages && ( ! ( $i >= $paged + $visible_range + 1 || $i <= $paged - $visible_range - 1 ) || $pages <= $visible_range ) ) {
1602 + if ( 1 != $pages && ( ! ( $i >= $paged + $range + 1 || $i <= $paged - $range - 1 ) || $pages <= $showitems ) ) {
2275 1603 $html .= ( $paged == $i ) ? '<li class="active"><span>' . $i . '</span></li>' : "<li><a data-paged='{$i}' href='" . get_pagenum_link( $i ) . "'>" . $i . '</a></li>';
2276 1604 }
2277 1605 }
2278 1606
2279 - if ( $paged < $pages && $visible_range < $pages ) {
1607 + if ( $paged < $pages && $showitems < $pages ) {
2280 1608 $p = $paged + 1;
2281 1609 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
2282 1610 }
2283 1611
2284 - if ( $paged < $pages - 1 && $paged + $visible_range < $pages && $visible_range < $pages ) {
1612 + if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $showitems < $pages ) {
2285 1613 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
2286 1614 }
2287 1615
2288 1616 $html .= '</ul>';
@@ -2338,8 +1666,9 @@
2338 1666 */
2339 1667 do_action( 'rtsb/elements/elementor/additional_action_buttons', $items );
2340 1668 $html .= ob_get_clean();
2341 1669
1670 + // $html .= self::get_action_button_by_type( $items, 'quick_checkout' );
2342 1671 $html .= self::get_action_button_by_type( $items, 'wishlist' );
2343 1672 $html .= self::get_action_button_by_type( $items, 'compare' );
2344 1673 $html .= self::get_action_button_by_type( $items, 'quick_view' );
2345 1674 $html .= '</ul>';
@@ -2352,8 +1681,9 @@
2352 1681 } elseif ( 'preset1' === $preset || 'preset2' === $preset || 'preset4' === $preset ) {
2353 1682 $html .= '<div class="rtsb-action-buttons ' . esc_attr( $class ) . '">';
2354 1683 $html .= '<ul class="rtsb-action-button-list">';
2355 1684 $html .= self::get_action_button_by_type( $items, 'add_to_cart', $ajax_cart );
1685 + // $html .= self::get_action_button_by_type( $items, 'quick_checkout' );
2356 1686
2357 1687 ob_start();
2358 1688 /**
2359 1689 * Additional formatted action buttons hook.
@@ -2394,8 +1724,10 @@
2394 1724 } else {
2395 1725 $class .= ' rtsb-' . esc_attr( str_replace( '_', '-', $type ) );
2396 1726 }
2397 1727
1728 + $html .= '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">';
1729 +
2398 1730 if ( ( 'add_to_cart' === $type ) && ( ! empty( $cart_html ) ) ) {
2399 1731 $html .= $cart_html;
2400 1732 } else {
2401 1733 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
@@ -2400,11 +1732,9 @@
2400 1732 } else {
2401 1733 $html .= shortcode_exists( 'rtsb_' . $type . '_button' ) ? do_shortcode( '[rtsb_' . $type . '_button]' ) : null;
2402 1734 }
2403 1735
2404 - if ( ! empty( $html ) ) {
2405 - $html = '<' . esc_attr( $wrapper ) . ' class="' . esc_attr( $class ) . '">' . $html . '</' . esc_attr( $wrapper ) . '>';
2406 - }
1736 + $html .= '</' . esc_attr( $wrapper ) . '>';
2407 1737
2408 1738 return apply_filters( 'rtsb/elements/elementor/get_action_button_by_type', $html );
2409 1739 }
2410 1740
@@ -2505,9 +1835,9 @@
2505 1835 $link['social_network'] = 'Facebook';
2506 1836 $link['social_action'] = 'Share';
2507 1837 break;
2508 1838 case 'twitter':
2509 - $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'] );
1839 + $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'] );
2510 1840 $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>';
2511 1841 $link['attr_title'] = esc_html__( 'Share on Twitter', 'shopbuilder' );
2512 1842 $link['social_network'] = 'Twitter';
2513 1843 $link['social_action'] = 'Tweet';
@@ -2541,9 +1871,9 @@
2541 1871 $link['social_action'] = 'Share';
2542 1872 break;
2543 1873 case 'reddit':
2544 1874 $link['link'] = esc_url( 'https://reddit.com/submit?url=' . $link['url'] . '&title=' . $link['title'] );
2545 - $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>';
1875 + $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>';
2546 1876 $link['attr_title'] = esc_html__( 'Share on Reddit', 'shopbuilder' );
2547 1877 $link['social_network'] = 'Reddit';
2548 1878 $link['social_action'] = 'Share';
2549 1879 break;
@@ -2631,87 +1961,33 @@
2631 1961
2632 1962 /**
2633 1963 * Social Share Platforms.
2634 1964 *
2635 - * @param string $module Module.
2636 - *
2637 - * @return true|void
1965 + * @return mixed|null
2638 1966 */
2639 1967 public static function is_module_active( $module ) {
2640 - $modulelist = self::get_modules_list();
2641 -
1968 + $modulelist = ModuleList::instance()->get_data();
2642 1969 if ( ! empty( $modulelist[ $module ]['active'] ) ) {
2643 1970 return true;
2644 1971 }
2645 1972 }
2646 - /**
2647 - * Checks if catalog pro is active
2648 - *
2649 - * @return boolean
2650 - */
2651 - public static function is_catalog_mode() {
2652 - return function_exists( 'rtsbpro' ) && self::is_module_active( 'catalog_mode' );
2653 - }
2654 - /**
2655 - * Checks if back in stock notifier is active
2656 - *
2657 - * @return boolean
2658 - */
2659 - public static function is_back_in_stock_notifier_enable() {
2660 - return function_exists( 'rtsbpro' ) && self::is_module_active( 'back_in_stock_notifier' );
2661 - }
2662 1973
2663 - /**
2664 - * Elementor Widget Active.
2665 - *
2666 - * @param string $widget Elementor Widget.
2667 - *
2668 - * @return true|void
2669 - */
2670 - public static function is_elementor_widget_active( $widget ) {
2671 - $element_list = self::get_widgets_list();
2672 1974
2673 - if ( ! empty( $element_list[ $widget ]['active'] ) ) {
2674 - return true;
2675 - }
2676 - }
2677 -
2678 - /**
2679 - * Sanitize a media (fileupload) control value into an { id, source } array.
2680 - *
2681 - * The control value is posted as an array (or a JSON string). Using it as a
2682 - * `sanitize_fn` avoids it being flattened to an empty string by the default
2683 - * sanitize_text_field() path in set_options().
2684 - *
2685 - * @param mixed $raw_value Raw posted value.
2686 - *
2687 - * @return array|string
2688 - */
2689 - public static function sanitize_fileupload_value( $raw_value ) {
2690 - $decoded = is_string( $raw_value ) && '' !== $raw_value ? json_decode( wp_unslash( $raw_value ), true ) : $raw_value;
2691 - if ( ! is_array( $decoded ) ) {
2692 - return '';
2693 - }
2694 - return [
2695 - 'id' => isset( $decoded['id'] ) ? absint( $decoded['id'] ) : 0,
2696 - 'source' => isset( $decoded['source'] ) ? esc_url_raw( $decoded['source'] ) : '',
2697 - ];
2698 - }
2699 -
2700 1975 /***
2701 1976 * Save Settings data
2702 1977 *
2703 - * @param string $section_id Section ID.
2704 - * @param string $block_id Block ID.
2705 - * @param array $rawOptions Raw Options.
1978 + * @param $section_id
1979 + * @param $block_id
1980 + * @param $rawOptions
2706 1981 *
2707 1982 * @return array
2708 1983 */
2709 - public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) { // phpcs:ignore Generic.Metrics.NestingLevel.TooHigh
1984 + public static function set_options( $section_id = '', $block_id = '', $rawOptions = [] ) {
2710 1985 $section_id = ! empty( $section_id ) ? sanitize_text_field( wp_unslash( $section_id ) ) : '';
2711 1986 $block_id = ! empty( $block_id ) ? sanitize_text_field( wp_unslash( $block_id ) ) : '';
2712 1987 $rawOptions = ! empty( $rawOptions ) ? $rawOptions : [];
2713 - $results = [
1988 +
1989 + $results = [
2714 1990 'status' => true,
2715 1991 'message' => '',
2716 1992 ];
2717 1993 if ( ! $section_id || ! $block_id ) {
@@ -2727,15 +2003,15 @@
2727 2003
2728 2004 return $results;
2729 2005 }
2730 2006
2731 - $options = DataModel::source()->get_option( $section_id, [], false );
2007 + $options = DataModel::source()->get_option( $section_id, [] );
2732 2008 $changed = false;
2733 2009 $fields = [];
2734 2010 if ( isset( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) && ! empty( $sections[ $section_id ]['list'][ $block_id ]['fields'] ) ) {
2735 2011 $fields = $sections[ $section_id ]['list'][ $block_id ]['fields'];
2736 2012 }
2737 - do_action( 'rtsb/before/save/options', $section_id, $block_id, $rawOptions );
2013 +
2738 2014 if ( empty( $fields ) ) {
2739 2015 if ( isset( $rawOptions['active'] ) ) {
2740 2016 $changed = true;
2741 2017 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
@@ -2741,49 +2017,21 @@
2741 2017 $options[ $block_id ]['active'] = 'on' === $rawOptions['active'] ? 'on' : '';
2742 2018 }
2743 2019 } else {
2744 2020 foreach ( $rawOptions as $raw_option_key => $raw_value ) {
2745 - if ( 'active' === $raw_option_key ) {
2021 + if ( $raw_option_key === 'active' ) {
2746 2022 $changed = true;
2747 - $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : ''; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
2023 + $options[ $block_id ]['active'] = $sections[ $section_id ]['list'][ $block_id ]['active'] = 'on' === $raw_value ? 'on' : '';
2748 2024 } else {
2749 2025 if ( isset( $fields[ $raw_option_key ] ) ) {
2750 2026 $field = $fields[ $raw_option_key ];
2751 - if ( 'switch' === $field['type'] ) {
2027 + if ( $field['type'] === 'switch' ) {
2752 2028 $value = 'on' === $raw_value ? 'on' : '';
2753 2029 } elseif ( 'repeaters' === $field['type'] ) {
2754 - $the_value = [];
2755 - $rep_title = [];
2756 - if ( ! empty( $raw_value ) && is_array( $raw_value ) ) {
2757 - foreach ( $raw_value as $key => $value ) {
2758 - if ( is_string( $value ) ) {
2759 - $the_value_decoded = json_decode( stripslashes_deep( $value ) );
2760 - } else {
2761 - $the_value_decoded = $value;
2762 - }
2763 - $cr = $the_value_decoded->title ?? '';
2764 - if ( in_array( $cr, $rep_title, true ) ) {
2765 - continue;
2766 - }
2767 - $rep_title[] = $cr;
2768 - $the_value[] = $the_value_decoded;
2769 - }
2770 - } elseif ( ! empty( $raw_value ) && is_string( $raw_value ) ) {
2771 - $the_value = $raw_value;
2772 - }
2773 - $value = wp_json_encode( $the_value, JSON_UNESCAPED_UNICODE );
2774 - } elseif ( in_array( $field['type'], [ 'product_addons_special_settings', 'checkout_fields' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2775 - $manual_field_value = [];
2776 - if ( is_array( $raw_value ) ) {
2777 - foreach ( $raw_value as $key => $value ) {
2778 - $manual_field_value[] = json_decode( stripslashes( $value ), true );
2779 - }
2780 - $value = wp_json_encode( $manual_field_value, JSON_UNESCAPED_UNICODE );
2781 - } elseif ( is_string( $raw_value ) ) {
2782 - $value = $raw_value;
2783 - }
2030 + $value = stripslashes_deep( $raw_value );
2784 2031 } else {
2785 - if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2032 + if ( ! empty( $field['multiple'] ) || in_array( $field['type'], [ 'checkbox', 'search_and_multi_select' ] ) ) {
2033 +
2786 2034 if ( isset( $raw_value ) && is_array( $raw_value ) ) {
2787 2035 if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2788 2036 $value = array_map( $field['sanitize_fn'], $raw_value );
2789 2037 } else {
@@ -2792,22 +2040,16 @@
2792 2040 } else {
2793 2041 $value = [];
2794 2042 }
2795 2043 } else {
2796 - if ( ! empty( $field['sanitize_fn'] ) ) {
2797 - if ( is_callable( $field['sanitize_fn'] ) ) {
2798 - $value = $field['sanitize_fn']( $raw_value );
2799 - } elseif ( 'pass_all' === $field['sanitize_fn'] ) {
2800 - $value = $raw_value;
2801 - } else {
2802 - $value = $field['sanitize_fn']( $raw_value );
2803 - }
2044 + if ( ! empty( $field['sanitize_fn'] ) && is_callable( $field['sanitize_fn'] ) ) {
2045 + $value = $field['sanitize_fn']( $raw_value );
2804 2046 } else {
2805 2047 $value = sanitize_text_field( $raw_value );
2806 2048 }
2807 2049 }
2808 2050 }
2809 - $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
2051 + $options[ $block_id ][ $raw_option_key ] = $sections[ $section_id ]['list'][ $block_id ]['fields'][ $raw_option_key ]['value'] = $value ?? null;
2810 2052 $changed = true;
2811 2053 }
2812 2054 }
2813 2055 }
@@ -2827,126 +2069,56 @@
2827 2069
2828 2070 return $results;
2829 2071 }
2830 2072
2831 - /**
2832 - * Count published products matching a tax query.
2073 + /***
2074 + * @param $meta_key
2075 + * @param $meta_value
2833 2076 *
2834 - * Filter badges must agree with the number of products the same filter lists.
2835 - * The archive (and the Ajax filter response) runs a plain WP_Query, while
2836 - * WC_Product_Query additionally restricts results to the registered
2837 - * `product_type` terms — so a product imported without a `product_type` term
2838 - * is listed on the archive but silently dropped from the count, making the
2839 - * badge read lower than the result set (e.g. "Cat1 (4)" for 10 results).
2077 + * @return mixed|void
2840 2078 *
2841 - * @param array $tax_query Tax query clauses.
2842 2079 *
2843 - * @return int Number of matching products.
2080 + * public static function get_post_id_by_meta_key_and_value( $meta_key, $meta_value ) {
2081 + * if( ! $meta_key || ! $meta_value ){
2082 + * return;
2083 + * }
2084 + * global $wpdb;
2085 + * $prepare_guery = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '%s' and meta_value = '%d'", $meta_key, $meta_value );
2086 + * $the_products = $wpdb->get_col( $prepare_guery );
2087 + * if( count( $the_products ) > 0 ){
2088 + * return $the_products[0];
2089 + * }
2090 + * return;
2091 + * }
2844 2092 */
2845 - public static function count_products_by_tax_query( array $tax_query ) {
2846 - if ( empty( $tax_query ) ) {
2847 - return 0;
2848 - }
2849 2093
2850 - $query = new WP_Query(
2851 - [
2852 - 'post_type' => 'product',
2853 - 'post_status' => 'publish',
2854 - 'posts_per_page' => -1,
2855 - 'fields' => 'ids',
2856 - 'ignore_sticky_posts' => true,
2857 - 'no_found_rows' => true,
2858 - 'update_post_meta_cache' => false,
2859 - 'update_post_term_cache' => false,
2860 - 'tax_query' => $tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2861 - ]
2862 - );
2863 -
2864 - return count( $query->posts );
2865 - }
2866 -
2867 - /**
2868 - * Count products by taxonomies.
2869 - *
2870 - * @param array $terms Terms.
2871 - * @param string $taxonomy Taxonomy.
2872 - *
2873 - * @return int|void
2874 - */
2875 2094 public static function count_products_by_taxonomies( $terms, $taxonomy = 'product_cat' ) {
2876 2095 if ( empty( $terms ) || ! is_array( $terms ) ) {
2877 2096 return;
2878 2097 }
2879 2098
2880 - $args = [
2881 - 'limit' => -1,
2882 - 'return' => 'ids',
2883 - ];
2099 + $category_ids = array_map(
2100 + function ( $name ) {
2101 + return sanitize_title( $name );
2102 + },
2103 + $terms
2104 + );
2884 2105
2885 - if ( 'product_cat' === $taxonomy ) {
2886 - $args['product_category_id'] = $terms;
2887 - } elseif ( 'product_brand' === $taxonomy ) {
2888 - $args['product_brand_id'] = $terms;
2889 - } else {
2890 - $args['product_tag_id'] = $terms;
2891 - }
2892 -
2893 - $query = new WC_Product_Query( $args );
2894 -
2895 - return ! empty( $query->get_products() ) ? count( $query->get_products() ) : 0;
2896 - }
2897 -
2898 - /**
2899 - * Count products by attribute terms.
2900 - *
2901 - * @param array $term_ids Term IDs.
2902 - * @param string $relation Relation.
2903 - *
2904 - * @return int
2905 - */
2906 - public static function count_products_by_attribute_terms( $term_ids, $relation = 'AND' ) {
2907 - if ( empty( $term_ids ) || ! is_array( $term_ids ) ) {
2908 - return 0;
2909 - }
2910 -
2911 - $terms_by_taxonomy = [];
2912 -
2913 - foreach ( $term_ids as $term_id ) {
2914 - $term = get_term( $term_id );
2915 - if ( ! is_wp_error( $term ) && $term ) {
2916 - if ( ! isset( $terms_by_taxonomy[ $term->taxonomy ] ) ) {
2917 - $terms_by_taxonomy[ $term->taxonomy ] = [];
2918 - }
2919 - $terms_by_taxonomy[ $term->taxonomy ][] = $term_id;
2920 - }
2921 - }
2922 -
2923 - if ( empty( $terms_by_taxonomy ) ) {
2924 - return 0;
2925 - }
2926 -
2927 2106 $args = [
2928 - 'status' => 'publish',
2929 - 'limit' => -1,
2930 - 'return' => 'ids',
2931 - 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2932 - 'relation' => strtoupper( $relation ),
2107 + 'post_type' => 'product',
2108 + 'posts_per_page' => - 1,
2109 + 'tax_query' => [
2110 + [
2111 + 'taxonomy' => $taxonomy,
2112 + 'field' => 'term_id',
2113 + 'terms' => $category_ids,
2114 + ],
2933 2115 ],
2934 2116 ];
2935 2117
2936 - foreach ( $terms_by_taxonomy as $taxonomy => $terms ) {
2937 - $args['tax_query'][] = [
2938 - 'taxonomy' => $taxonomy,
2939 - 'field' => 'term_id',
2940 - 'terms' => $terms,
2941 - 'operator' => 'IN',
2942 - ];
2943 - }
2118 + $query = new \WP_Query( $args );
2944 2119
2945 - $query = new WC_Product_Query( $args );
2946 - $products = $query->get_products();
2947 -
2948 - return count( $products );
2120 + return $query->found_posts;
2949 2121 }
2950 2122
2951 2123 /**
2952 2124 * Check if product filters widget has ajax.
@@ -2959,20 +2131,9 @@
2959 2131 if ( empty( $page ) ) {
2960 2132 return false;
2961 2133 }
2962 2134
2963 - if ( 'page' === get_post_type() ) {
2964 - return false;
2965 - }
2966 -
2967 - $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2968 - if ( ! $id ) {
2969 - return false;
2970 - }
2971 - $cache_key = 'product_filters_has_ajax_' . $id;
2972 - if ( isset( self::$cache[ $cache_key ] ) ) {
2973 - return self::$cache[ $cache_key ];
2974 - }
2135 + $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
2975 2136 $elmap = ElementorDataMap::instance();
2976 2137 $ajax = [];
2977 2138
2978 2139 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
@@ -2978,238 +2139,12 @@
2978 2139 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
2979 2140 $ajax[] = isset( $data['settings']['ajax_mode'] ) ? false : true;
2980 2141 }
2981 2142
2982 - self::$cache[ $cache_key ] = ! empty( $ajax[0] );
2983 2143 return ! empty( $ajax[0] );
2984 2144 }
2985 2145
2986 2146 /**
2987 - * Build the active filter chips HTML from the current request query string.
2988 - *
2989 - * Mirrors the markup produced by the JS `displayAppliedFilter()` method so
2990 - * the chips are visible on initial page load (before AJAX hydration).
2991 - *
2992 - * @return string HTML for the active filter chips, or empty string if none.
2993 - */
2994 - public static function get_active_filters_html() {
2995 - // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only query string parsing for display.
2996 - if ( empty( $_GET ) ) {
2997 - return '';
2998 - }
2999 -
3000 - $skip_keys = [
3001 - 'displayview',
3002 - 'orderby',
3003 - 'paged',
3004 - 'page',
3005 - 'product-page',
3006 - 'rtsb_archive_page',
3007 - 'rtsb_grid_columns',
3008 - 'min_price',
3009 - 'max_price',
3010 - '_pjax',
3011 - ];
3012 -
3013 - // When the "Product Categories" widget is already showing the active
3014 - // category navigation, suppress the duplicate product_cat chip group.
3015 - if ( self::is_product_categories_widget_active() ) {
3016 - $skip_keys[] = 'product_cat';
3017 - }
3018 -
3019 - $groups = [];
3020 -
3021 - foreach ( $_GET as $raw_key => $raw_value ) {
3022 - $key = sanitize_key( $raw_key );
3023 -
3024 - if ( '' === $key || in_array( $key, $skip_keys, true ) ) {
3025 - continue;
3026 - }
3027 -
3028 - if ( 0 === strpos( $key, 'query_type_' ) ) {
3029 - continue;
3030 - }
3031 -
3032 - if ( is_array( $raw_value ) ) {
3033 - continue;
3034 - }
3035 -
3036 - $value = sanitize_text_field( wp_unslash( $raw_value ) );
3037 -
3038 - if ( '' === $value ) {
3039 - continue;
3040 - }
3041 -
3042 - $taxonomy = $key;
3043 - if ( 0 === strpos( $taxonomy, 'filter_' ) ) {
3044 - $taxonomy = 'pa_' . substr( $taxonomy, 7 );
3045 - }
3046 -
3047 - $items = [];
3048 - foreach ( explode( ',', $value ) as $slug ) {
3049 - $slug = trim( $slug );
3050 - if ( '' === $slug ) {
3051 - continue;
3052 - }
3053 - $items[] = [
3054 - 'slug' => $slug,
3055 - 'name' => self::get_active_filter_term_label( $taxonomy, $slug, $key ),
3056 - ];
3057 - }
3058 -
3059 - if ( empty( $items ) ) {
3060 - continue;
3061 - }
3062 -
3063 - $groups[ $key ] = [
3064 - 'label' => self::get_active_filter_group_label( $taxonomy, $key ),
3065 - 'items' => $items,
3066 - 'class' => $key,
3067 - ];
3068 - }
3069 -
3070 - // Price range as a single combined chip.
3071 - if ( isset( $_GET['min_price'] ) || isset( $_GET['max_price'] ) ) {
3072 - $min = isset( $_GET['min_price'] ) ? sanitize_text_field( wp_unslash( $_GET['min_price'] ) ) : '';
3073 - $max = isset( $_GET['max_price'] ) ? sanitize_text_field( wp_unslash( $_GET['max_price'] ) ) : '';
3074 -
3075 - if ( '' !== $min || '' !== $max ) {
3076 - $symbol = function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '';
3077 - $groups['price_filter'] = [
3078 - 'label' => esc_html__( 'Price', 'shopbuilder' ),
3079 - 'class' => 'price_filter',
3080 - 'items' => [
3081 - [
3082 - 'slug' => $min . ',' . $max,
3083 - 'name' => $symbol . $min . ' - ' . $symbol . $max,
3084 - 'extra_class' => ' remove-price',
3085 - ],
3086 - ],
3087 - ];
3088 - }
3089 - }
3090 -
3091 - if ( empty( $groups ) ) {
3092 - return '';
3093 - }
3094 -
3095 - $html = '<div class="rtsb-active-filters">';
3096 -
3097 - foreach ( $groups as $key => $group ) {
3098 - $html .= '<div class="active-filter ' . esc_attr( $group['class'] ) . '">';
3099 -
3100 - if ( ! empty( $group['label'] ) ) {
3101 - $html .= '<div class="filter-name">' . esc_html( $group['label'] ) . ': </div>';
3102 - }
3103 -
3104 - $html .= '<div class="filter-item-container">';
3105 -
3106 - foreach ( $group['items'] as $item ) {
3107 - $extra = isset( $item['extra_class'] ) ? $item['extra_class'] : '';
3108 - $html .= '<div class="filter-item">' . esc_html( $item['name'] );
3109 - $html .= '<span class="remove-filter' . esc_attr( $extra ) . '"';
3110 - $html .= ' title="' . esc_attr( sprintf( /* translators: %s: filter term name */ __( 'Remove %s', 'shopbuilder' ), $item['name'] ) ) . '"';
3111 - $html .= ' data-filter-name="' . esc_attr( $key ) . '"';
3112 - $html .= ' data-filter-value="' . esc_attr( $item['slug'] ) . '">';
3113 - $html .= '<i class="rtsb-icon rtsb-icon-delete"></i></span>';
3114 - $html .= '</div>';
3115 - }
3116 -
3117 - $html .= '</div></div>';
3118 - }
3119 -
3120 - $html .= '<a href="#" class="rtsb-clear-filters"><span class="icon-wrap"><i class="eicon-trash-o"></i></span><span>' . esc_html__( 'Reset Filters', 'shopbuilder' ) . '</span></a>';
3121 - $html .= '</div>';
3122 -
3123 - return $html;
3124 - // phpcs:enable
3125 - }
3126 -
3127 - /**
3128 - * Determine whether the "Product Categories" Elementor widget is present
3129 - * on the current shop/archive layout. Used to avoid duplicating the
3130 - * product_cat chip group when the widget already shows the active term.
3131 - *
3132 - * @return bool
3133 - */
3134 - private static function is_product_categories_widget_active() {
3135 - $elmap = ElementorDataMap::instance();
3136 -
3137 - foreach ( [ 'shop', 'archive' ] as $page ) {
3138 - $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
3139 -
3140 - if ( ! $id ) {
3141 - continue;
3142 - }
3143 -
3144 - $widgets = $elmap->get_widget_data( 'rtsb-product-categories-general', [], $id );
3145 -
3146 - if ( ! empty( $widgets ) ) {
3147 - return true;
3148 - }
3149 - }
3150 -
3151 - return false;
3152 - }
3153 -
3154 - /**
3155 - * Resolve the display label for an active filter group (taxonomy heading).
3156 - *
3157 - * @param string $taxonomy Resolved taxonomy slug (e.g. product_cat, pa_color).
3158 - * @param string $raw_key Original URL key (e.g. product_cat, filter_color).
3159 - *
3160 - * @return string
3161 - */
3162 - private static function get_active_filter_group_label( $taxonomy, $raw_key ) {
3163 - if ( 's' === $raw_key || 'search' === $raw_key ) {
3164 - return esc_html__( 'Search', 'shopbuilder' );
3165 - }
3166 - if ( 'rating_filter' === $raw_key ) {
3167 - return esc_html__( 'Ratings', 'shopbuilder' );
3168 - }
3169 - if ( 'sale_filter' === $raw_key ) {
3170 - return esc_html__( 'Sale Filter', 'shopbuilder' );
3171 - }
3172 -
3173 - if ( 0 === strpos( $taxonomy, 'pa_' ) && function_exists( 'wc_attribute_label' ) ) {
3174 - return wc_attribute_label( $taxonomy );
3175 - }
3176 -
3177 - if ( taxonomy_exists( $taxonomy ) ) {
3178 - $tax_obj = get_taxonomy( $taxonomy );
3179 - if ( $tax_obj && ! empty( $tax_obj->labels->name ) ) {
3180 - return $tax_obj->labels->name;
3181 - }
3182 - }
3183 -
3184 - return ucwords( str_replace( [ '_', '-' ], ' ', $raw_key ) );
3185 - }
3186 -
3187 - /**
3188 - * Resolve a term slug to its display name; fall back to a humanised slug.
3189 - *
3190 - * @param string $taxonomy Resolved taxonomy slug.
3191 - * @param string $slug Raw slug from query string.
3192 - * @param string $raw_key Original URL key.
3193 - *
3194 - * @return string
3195 - */
3196 - private static function get_active_filter_term_label( $taxonomy, $slug, $raw_key ) {
3197 - if ( 's' === $raw_key || 'search' === $raw_key || 'rating_filter' === $raw_key ) {
3198 - return rawurldecode( $slug );
3199 - }
3200 -
3201 - if ( taxonomy_exists( $taxonomy ) ) {
3202 - $term = get_term_by( 'slug', $slug, $taxonomy );
3203 - if ( $term && ! is_wp_error( $term ) ) {
3204 - return $term->name;
3205 - }
3206 - }
3207 -
3208 - return ucwords( str_replace( [ '-', '_' ], ' ', rawurldecode( $slug ) ) );
3209 - }
3210 -
3211 - /**
3212 2147 * Check if product has applied filter.
3213 2148 *
3214 2149 * @param string $page Page name.
3215 2150 *
@@ -3222,23 +2157,14 @@
3222 2157
3223 2158 $id = BuilderFns::is_builder_preview() ? get_the_ID() : BuilderFns::builder_page_id_by_type( $page );
3224 2159 $elmap = ElementorDataMap::instance();
3225 2160 $ajax = [];
3226 - if ( ! $id ) {
3227 - return false;
3228 - }
2161 +
3229 2162 foreach ( $elmap->get_widget_data( 'rtsb-ajax-product-filters', [], $id ) as $data ) {
3230 2163 $ajax[] = ! isset( $data['settings']['active_filter'] );
3231 2164
3232 2165 }
3233 2166
3234 - // Also account for the free, non-AJAX "Product Filters" widget so that
3235 - // the active filter chips wrapper renders for it on initial page load.
3236 - foreach ( $elmap->get_widget_data( 'rtsb-product-filters', [], $id ) as $data ) {
3237 - $ajax[] = true;
3238 - unset( $data );
3239 - }
3240 -
3241 2167 return ! empty( $ajax[0] );
3242 2168 }
3243 2169
3244 2170 /**
@@ -3248,94 +2174,24 @@
3248 2174 *
3249 2175 * @return array An array of product categories with 'value' and 'label' keys.
3250 2176 */
3251 2177 public static function products_category_query( $search_query = null ) {
3252 - if ( ! is_admin() ) {
3253 - return [];
3254 - }
2178 + global $wpdb;
3255 2179
3256 - $cache_key = 'rtsb_product_categories_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
3257 -
3258 - if ( isset( self::$cache[ $cache_key ] ) ) {
3259 - return self::$cache[ $cache_key ];
3260 - }
3261 - $results = wp_cache_get( $cache_key, Cache::group() );
3262 - if ( ! $results ) {
3263 - global $wpdb;
3264 - $sql = "SELECT t.term_id, t.name
2180 + $sql = "SELECT t.term_id, t.name
3265 2181 FROM {$wpdb->terms} t
3266 2182 JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
3267 2183 WHERE tt.taxonomy = 'product_cat'";
3268 2184
3269 - if ( $search_query ) {
3270 - $sql .= ' AND t.name LIKE %s';
3271 - $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3272 - } else {
3273 - $prepared_sql = $sql; // No need for placeholders.
3274 - }
3275 -
3276 - $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
3277 - // Cache the results in the object cache for future use.
3278 - wp_cache_set( $cache_key, $results, Cache::group(), 12 * HOUR_IN_SECONDS );
3279 - Cache::set_data_cache_key( $cache_key );
2185 + if ( $search_query ) {
2186 + $sql .= ' AND t.name LIKE %s';
2187 + $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // Escaping and wildcard
2188 + } else {
2189 + $prepared_sql = $sql; // No need for placeholders
3280 2190 }
3281 2191
3282 - $cats = [];
3283 - if ( ! is_array( $results ) && ! count( $results ) ) {
3284 - return $cats;
3285 - }
3286 - foreach ( $results as $row ) {
3287 - $category_id = $row->term_id;
3288 - $category_title = $row->name;
2192 + $results = $wpdb->get_results( $prepared_sql );
3289 2193
3290 - $cats[] = [
3291 - 'value' => $category_id,
3292 - 'label' => $category_title,
3293 - ];
3294 - }
3295 -
3296 - // Cache the results in a static variable for the next call.
3297 - self::$cache[ $cache_key ] = $cats;
3298 - return $cats;
3299 - }
3300 - /**
3301 - * Get WooCommerce product categories.
3302 - *
3303 - * @param string|null $search_query The search string for category names.
3304 - *
3305 - * @return array An array of product categories with 'value' and 'label' keys.
3306 - */
3307 - public static function products_tags_query( $search_query = null ) {
3308 - if ( ! is_admin() ) {
3309 - return [];
3310 - }
3311 -
3312 - $cache_key = 'rtsb_product_tags_' . md5( serialize( $search_query ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
3313 -
3314 - if ( isset( self::$cache[ $cache_key ] ) ) {
3315 - return self::$cache[ $cache_key ];
3316 - }
3317 - $results = wp_cache_get( $cache_key, Cache::group() );
3318 - if ( ! $results ) {
3319 - global $wpdb;
3320 - $sql = "SELECT t.term_id, t.name
3321 - FROM {$wpdb->terms} t
3322 - JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
3323 - WHERE tt.taxonomy = 'product_tag'";
3324 -
3325 - if ( $search_query ) {
3326 - $sql .= ' AND t.name LIKE %s';
3327 - $prepared_sql = $wpdb->prepare( $sql, '%' . $wpdb->esc_like( $search_query ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
3328 - } else {
3329 - $prepared_sql = $sql; // No need for placeholders.
3330 - }
3331 -
3332 - $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
3333 - // Cache the results in the object cache for future use.
3334 - wp_cache_set( $cache_key, $results, Cache::group(), 12 * HOUR_IN_SECONDS );
3335 - Cache::set_data_cache_key( $cache_key );
3336 - }
3337 -
3338 2194 $cats = [];
3339 2195 if ( ! is_array( $results ) && ! count( $results ) ) {
3340 2196 return $cats;
3341 2197 }
@@ -3348,20 +2204,18 @@
3348 2204 'label' => $category_title,
3349 2205 ];
3350 2206 }
3351 2207
3352 - // Cache the results in a static variable for the next call.
3353 - self::$cache[ $cache_key ] = $cats;
3354 2208 return $cats;
3355 2209 }
3356 2210
3357 2211 /**
3358 - * @param string $search_query Search query.
2212 + * @param $search_query
3359 2213 *
3360 2214 * @return array
3361 2215 */
3362 2216 public static function get_registered_post_types( $search_query = null ) {
3363 - // Get all registered post types.
2217 + // Get all registered post types
3364 2218 $registered_post_types = get_post_types(
3365 2219 [
3366 2220 'public' => true,
3367 2221 '_builtin' => false,
@@ -3374,11 +2228,11 @@
3374 2228 unset( $registered_post_types['rtsb_builder'] );
3375 2229
3376 2230 $post_types = [];
3377 2231
3378 - // Check if a search query is provided.
2232 + // Check if a search query is provided
3379 2233 if ( ! empty( $search_query ) ) {
3380 - // Filter post types based on the search query.
2234 + // Filter post types based on the search query
3381 2235 $registered_post_types = array_filter(
3382 2236 $registered_post_types,
3383 2237 function ( $post_type ) use ( $search_query ) {
3384 2238 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
@@ -3383,9 +2237,9 @@
3383 2237 function ( $post_type ) use ( $search_query ) {
3384 2238 return stripos( $post_type->labels->singular_name, $search_query ) !== false;
3385 2239 }
3386 2240 );
3387 - // Check if 'post' match the search query and include them if they do.
2241 + // Check if 'post' match the search query and include them if they do
3388 2242 if ( stripos( 'post', $search_query ) !== false ) {
3389 2243 $post_types[] = [
3390 2244 'value' => 'post',
3391 2245 'label' => 'Post',
@@ -3397,9 +2251,9 @@
3397 2251 'label' => 'Post',
3398 2252 ];
3399 2253 }
3400 2254
3401 - // Convert the filtered post types to an array.
2255 + // Convert the filtered post types to an array
3402 2256 foreach ( $registered_post_types as $post_type ) {
3403 2257 $post_types[] = [
3404 2258 'value' => $post_type->name,
3405 2259 'label' => $post_type->labels->singular_name,
@@ -3411,10 +2265,9 @@
3411 2265
3412 2266 /**
3413 2267 * Get Post Types.
3414 2268 *
3415 - * @param string $search_query Search query.
3416 - * @param array $args Arguments.
2269 + * @param $search_query
3417 2270 *
3418 2271 * @return array
3419 2272 */
3420 2273 public static function get_post_types( $search_query = null, $args = [] ) {
@@ -3437,12 +2290,11 @@
3437 2290 $query_results = get_posts( $args );
3438 2291 foreach ( $query_results as $post ) {
3439 2292 $posts[] = [
3440 2293 'value' => $post->ID,
3441 - 'label' => $post->post_title . ' (ID#' . $post->ID . ')',
2294 + 'label' => $post->post_title,
3442 2295 ];
3443 2296 }
3444 -
3445 2297 if ( $search_query ) {
3446 2298 if ( strpos( '-error 404', $search_query ) ) {
3447 2299 $posts[] = [
3448 2300 'value' => 'error',
@@ -3449,14 +2301,12 @@
3449 2301 'label' => __( '404 Error', 'shopbuilder' ),
3450 2302 ];
3451 2303 }
3452 2304 } else {
3453 - if ( 'page' === $args['post_type'] ) {
3454 - $posts[] = [
3455 - 'value' => 'error',
3456 - 'label' => __( '404 Error', 'shopbuilder' ),
3457 - ];
3458 - }
2305 + $posts[] = [
2306 + 'value' => 'error',
2307 + 'label' => __( '404 Error', 'shopbuilder' ),
2308 + ];
3459 2309 }
3460 2310 } else {
3461 2311 $posts = self::get_registered_post_types( $search_query );
3462 2312 }
@@ -3470,1088 +2320,6 @@
3470 2320 * @return array
3471 2321 */
3472 2322 public static function get_elementor_breakpoints() {
3473 2323 return ( new \Elementor\Core\Breakpoints\Manager() )->get_breakpoints_config();
3474 - }
3475 -
3476 - /**
3477 - * Render view.
3478 - *
3479 - * @param string $viewName View name.
3480 - * @param array $args View args.
3481 - * @param boolean $return View return.
3482 - * @return string|void
3483 - */
3484 - public static function renderView( $viewName, $args = [], $return = false ) {
3485 - $viewName = str_replace( '.', '/', $viewName );
3486 -
3487 - if ( ! empty( $args ) && is_array( $args ) ) {
3488 - extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
3489 - }
3490 -
3491 - $view_file = rtsb()->plugin_path() . '/resources/' . $viewName . '.php';
3492 -
3493 - if ( ! file_exists( $view_file ) ) {
3494 - _doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $view_file ) ), '1.7.0' );
3495 -
3496 - return;
3497 - }
3498 -
3499 - if ( $return ) {
3500 - ob_start();
3501 - include $view_file;
3502 -
3503 - return ob_get_clean();
3504 - } else {
3505 - include $view_file;
3506 - }
3507 - }
3508 -
3509 - /**
3510 - * Best selling product query.
3511 - *
3512 - * @param int $minimum_sale Minimum sale.
3513 - * @param int $limit Total limit.
3514 - *
3515 - * @return array|mixed
3516 - */
3517 - public static function best_selling_products_ids( $minimum_sale = 1, $limit = 10 ) {
3518 - $cache_key = 'rtsb_best_selling_products_' . $minimum_sale . '_' . $limit;
3519 - // Check if the data is in the cache.
3520 - if ( isset( self::$cache[ $cache_key ] ) ) {
3521 - return self::$cache[ $cache_key ];
3522 - }
3523 - $cached_data = get_transient( $cache_key );
3524 - if ( $cached_data ) {
3525 - self::$cache[ $cache_key ] = $cached_data;
3526 - return $cached_data;
3527 - }
3528 - global $wpdb;
3529 - $sql = $wpdb->prepare(
3530 - "
3531 - SELECT ID
3532 - FROM {$wpdb->posts} AS p
3533 - LEFT JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id
3534 - WHERE p.post_type = 'product'
3535 - AND p.post_status = 'publish'
3536 - AND pm.meta_key = 'total_sales'
3537 - AND pm.meta_value >= %d
3538 - ORDER BY pm.meta_value + 0 DESC
3539 - LIMIT %d
3540 - ",
3541 - $minimum_sale,
3542 - $limit
3543 - );
3544 - $best_selling = $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
3545 - // Cache the result for future use.
3546 - set_transient( $cache_key, $best_selling, DAY_IN_SECONDS );
3547 - self::$cache[ $cache_key ] = $best_selling;
3548 - return $best_selling;
3549 - }
3550 -
3551 - /**
3552 - * @param null|Object $product Product Object.
3553 - * @param int $days_threshold Date String.
3554 - *
3555 - * @return bool
3556 - */
3557 - public static function is_new_product( $product = null, $days_threshold = 30 ) {
3558 - if ( is_null( $product ) ) {
3559 - global $product;
3560 - }
3561 - if ( ! $product instanceof WC_Product ) {
3562 - return false;
3563 - }
3564 -
3565 - $product_id = $product->get_id();
3566 - $days_threshold = ! empty( $days_threshold ) ? $days_threshold : 30;
3567 - $cache_key = 'is_new_product_' . $product_id . '_' . $days_threshold;
3568 -
3569 - if ( isset( self::$cache[ $cache_key ] ) ) {
3570 - return self::$cache[ $cache_key ];
3571 - }
3572 - $date_created = $product->get_date_created();
3573 - if ( ! $date_created ) {
3574 - return false;
3575 - }
3576 - $publish_timestamp = strtotime( $date_created->date_i18n( 'Y-m-d H:i:s' ) );
3577 -
3578 - // Calculate the difference in days.
3579 - $days_difference = abs( time() - $publish_timestamp ) / ( 60 * 60 * 24 );
3580 -
3581 - // Check if the product is considered new.
3582 - $is_new = $days_difference <= $days_threshold;
3583 -
3584 - // Cache the result for a day.
3585 - self::$cache[ $cache_key ] = $is_new;
3586 -
3587 - return $is_new;
3588 - }
3589 -
3590 - /**
3591 - * Checks if a feature is disabled for guest users based on the provided option.
3592 - *
3593 - * @param string $option The option to retrieve from the item.
3594 - * @param mixed $default The default value if the option is not found.
3595 - *
3596 - * @return bool
3597 - */
3598 - public static function is_guest_feature_disabled( $option, $default ) {
3599 - $disabled = self::get_option( 'general', 'guest_user', $option, $default );
3600 -
3601 - return ! is_user_logged_in() && $disabled;
3602 - }
3603 -
3604 - /**
3605 - * Checks if a feature is disabled for guest users based on the provided option.
3606 - *
3607 - * @return void
3608 - */
3609 - public static function woocommerce_output_all_notices() {
3610 - if ( function_exists( 'wc_print_notices' ) ) :
3611 - echo '<div class="rtsb-notice">';
3612 - woocommerce_output_all_notices();
3613 - echo '</div>';
3614 - endif;
3615 - }
3616 -
3617 - /**
3618 - * @param object $product Product.
3619 - * @return bool
3620 - */
3621 - public static function is_visible_qty_input( $product = null ) {
3622 - $is_visible_qty = true;
3623 - if ( $product instanceof \WC_Product ) {
3624 - if ( $product->is_sold_individually() ) {
3625 - $is_visible_qty = false;
3626 - } elseif ( $product->managing_stock() ) {
3627 - if ( in_array( $product->get_backorders(), [ 'notify' , 'yes' ], true ) ) {
3628 - $is_visible_qty = true;
3629 - } elseif ( $product->get_stock_quantity() < 2 ) {
3630 - $is_visible_qty = false;
3631 - }
3632 - }
3633 - }
3634 - return $is_visible_qty;
3635 - }
3636 -
3637 - /**
3638 - * @param int $object_id Object id.
3639 - * @param string $element_type element type.
3640 - * @param string|null $current_lang null for current language, default for default languages.
3641 - * @return false|mixed|null
3642 - */
3643 - public static function wpml_object_id( $object_id, $element_type, $current_lang = 'default' ) {
3644 - if ( ! defined( 'ICL_SITEPRESS_VERSION' ) ) {
3645 - return $object_id;
3646 - }
3647 - if ( ! $object_id || ! $element_type ) {
3648 - return $object_id;
3649 - }
3650 - if ( 'default' === $current_lang ) {
3651 - $lang = apply_filters( 'wpml_default_language', null );
3652 - } else {
3653 - $lang = null;
3654 - }
3655 - return apply_filters( 'wpml_object_id', $object_id, $element_type, false, $lang );
3656 - }
3657 -
3658 - /**
3659 - * @return string
3660 - */
3661 - public static function wpml_current_language() {
3662 - $current = apply_filters( 'wpml_current_language', null );
3663 - $default = apply_filters( 'wpml_default_language', null );
3664 - return $default !== $current ? $current : null;
3665 - }
3666 -
3667 - /**
3668 - * Custom icons.
3669 - *
3670 - * @return string[]
3671 - */
3672 - public static function get_custom_icon_names() {
3673 - return [
3674 - 'heart-empty',
3675 - 'heart',
3676 - 'eye',
3677 - 'exchange',
3678 - 'plus',
3679 - 'minus',
3680 - 'avatar',
3681 - 'pay',
3682 - 'share',
3683 - 'clock',
3684 - 'check-alt',
3685 - 'check',
3686 - 'delete',
3687 - 'marker',
3688 - 'list',
3689 - 'list-2',
3690 - 'power',
3691 - 'cart',
3692 - 'cart-2',
3693 - 'cart-3',
3694 - 'downloads',
3695 - 'zoom',
3696 - 'user-edit',
3697 - 'grid',
3698 - 'filter',
3699 - 'billing',
3700 - 'login',
3701 - 'payment',
3702 - 'search',
3703 - 'edit',
3704 - 'coupon',
3705 - 'arrows-cw',
3706 - 'trash-empty',
3707 - ];
3708 - }
3709 -
3710 - /**
3711 - * Retrieve an array of custom icons with their HTML representation.
3712 - *
3713 - * @return array
3714 - */
3715 - public static function get_icons() {
3716 - $icon = self::get_custom_icon_names();
3717 - $icons_array = [];
3718 - foreach ( $icon as $value ) {
3719 - $icons_array[ $value ] = '<i class="rtsb-icon rtsb-icon-' . $value . '"></i> <span class="icon-name">' . ucfirst( str_replace( '-', ' ', $value ) ) . '</span>';
3720 - }
3721 - return $icons_array;
3722 - }
3723 -
3724 - /**
3725 - * Generates HTML markup for displaying an endpoint icon.
3726 - *
3727 - * @param array $data The endpoint data containing icon information.
3728 - * @param boolean $wrapper Whether to wrap the icon in a span element.
3729 - *
3730 - * @return string
3731 - */
3732 - public static function get_icon_html( $data, $wrapper = true ) {
3733 - if ( empty( $data ) || 'none' === $data['icon_source'] ) {
3734 - return '';
3735 - }
3736 - $endpoint = '';
3737 - $html = $wrapper ? '<span class="icon ' . esc_attr( $endpoint ) . '_icon">' : '';
3738 -
3739 - if ( 'select_icon' === $data['icon_source'] ) {
3740 - $html .= '<i class="rtsb-icon rtsb-icon-' . esc_attr( $data['custom_icon'] ) . '"></i>';
3741 - } else {
3742 - $icon_html = '';
3743 - $icon_url = $data['image_icon']['source'] ?? '';
3744 - $icon_id = $data['image_icon']['id'] ?? 0;
3745 - $extension = ! empty( $icon_url ) ? strtolower( substr( strrchr( $data['image_icon']['source'], '.' ), 1 ) ) : '';
3746 -
3747 - if ( 'svg' === $extension ) {
3748 - $content = file_get_contents( $icon_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
3749 - $is_svg = strpos( $content, '<svg' );
3750 -
3751 - if ( false !== $is_svg ) {
3752 - $icon_html = substr( $content, $is_svg );
3753 - }
3754 - } else {
3755 - $attr = [
3756 - 'class' => 'rtsb-icon-img',
3757 - 'alt' => esc_html( ucfirst( $endpoint ) ) . esc_html__( ' Icon', 'shopbuilder' ),
3758 - ];
3759 -
3760 - $icon_html = wp_get_attachment_image( absint( $icon_id ), 'full', true, $attr );
3761 - }
3762 -
3763 - $html .= $icon_html;
3764 - }
3765 -
3766 - $html .= $wrapper ? '</span>' : '';
3767 -
3768 - return $html;
3769 - }
3770 - /**
3771 - * Flushes rewrite rules.
3772 - *
3773 - * @return void
3774 - */
3775 - public static function maybe_flush_rewrite_rules() {
3776 - add_action( 'wp_loaded', [ __CLASS__, 'flush_rewrite_rules' ] );
3777 - add_action( 'shutdown', [ __CLASS__, 'flush_rewrite_rules_once_more' ] );
3778 - }
3779 -
3780 - /**
3781 - * Flushes rewrite rules if needed.
3782 - *
3783 - * @return void
3784 - */
3785 - public static function flush_rewrite_rules() {
3786 - if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3787 - flush_rewrite_rules();
3788 - }
3789 -
3790 - if ( get_option( 'rtsb_permalinks_flushed' ) === 'yes' ) {
3791 - flush_rewrite_rules();
3792 - delete_option( 'rtsb_permalinks_flushed' );
3793 - }
3794 - }
3795 -
3796 - /**
3797 - * Flushes rewrite rules if needed for second time.
3798 - *
3799 - * @return void
3800 - */
3801 - public static function flush_rewrite_rules_once_more() {
3802 - if ( get_option( 'rtsb_permalinks_need_flush' ) === 'yes' ) {
3803 - flush_rewrite_rules();
3804 - update_option( 'rtsb_permalinks_flushed', 'yes' );
3805 - delete_option( 'rtsb_permalinks_need_flush' );
3806 - }
3807 - }
3808 -
3809 - /**
3810 - * Social Share Platforms.
3811 - *
3812 - * @param string $section_id Section ID.
3813 - * @param string $block_id Block ID.
3814 - * @param string $option_key Option key.
3815 - * @param string $compare_key Compare key.
3816 - * @param string $compare_value Compare value.
3817 - * @param array $values Values.
3818 - *
3819 - * @return void
3820 - */
3821 - public static function set_repeater_options( $section_id, $block_id, $option_key, $compare_key, $compare_value, $values ) {
3822 - $modules = DataModel::source()->get_option( $section_id, [], false );
3823 - if ( empty( $modules[ $block_id ] ) ) {
3824 - return;
3825 - }
3826 - $options = $modules[ $block_id ];
3827 - if ( empty( $options[ $option_key ] ) ) {
3828 - return;
3829 - }
3830 - $repeater_options = json_decode( $options[ $option_key ], true );
3831 - // Check if options are not empty and are in array format.
3832 - if ( ! is_array( $repeater_options ) ) {
3833 - return;
3834 - }
3835 - // Use array_column to get an array of values from $compare_key.
3836 - $column_values = array_column( $repeater_options, $compare_key );
3837 - // Use array_search to find the index of the $compare_value.
3838 - $index = array_search( $compare_value, $column_values, true );
3839 - if ( false === $index ) {
3840 - return;
3841 - }
3842 - $repeater_options[ $index ] = wp_parse_args( $values, $repeater_options[ $index ] );
3843 - $options[ $option_key ] = wp_json_encode( $repeater_options );
3844 - $modules[ $block_id ] = $options;
3845 - DataModel::source()->set_option( $section_id, $modules );
3846 - }
3847 -
3848 -
3849 - /**
3850 - * @param array $ids products id.
3851 - * @return array
3852 - */
3853 - public static function get_available_products_by_ids( $ids = [] ) {
3854 - $ids = array_filter(
3855 - $ids,
3856 - function ( $id ) {
3857 - return 'product' === get_post_type( $id ) && 'publish' === get_post_status( $id );
3858 - }
3859 - );
3860 - return $ids;
3861 - }
3862 -
3863 - /**
3864 - * Generate and cache dynamic CSS styles.
3865 - *
3866 - * @param array $options CSS options to apply (e.g. padding, margin).
3867 - * @param string $cache_key Cache key for the CSS.
3868 - * @param array $css_properties An array of CSS properties with selectors and properties.
3869 - *
3870 - * @return void
3871 - */
3872 - public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3873 - $cached_css = wp_cache_get( $cache_key, Cache::group() );
3874 - $style_handle = self::optimized_handle( 'rtsb-frontend' );
3875 -
3876 - if ( false !== $cached_css ) {
3877 - wp_add_inline_style( $style_handle, $cached_css );
3878 - return;
3879 - }
3880 -
3881 - $css_rules = [];
3882 - $grouped_css_rules = [];
3883 -
3884 - foreach ( $css_properties as $option => $details ) {
3885 - if ( ! empty( $options[ $option ] ) ) {
3886 - $selector = $details['selector'] ?? '';
3887 - $property = $details['property'] ?? '';
3888 - $unit = $details['unit'] ?? '';
3889 - $value = $options[ $option ];
3890 -
3891 - if ( empty( $grouped_css_rules[ $selector ] ) ) {
3892 - $grouped_css_rules[ $selector ] = [];
3893 - }
3894 -
3895 - if ( is_array( $property ) ) {
3896 - foreach ( $property as $prop ) {
3897 - $grouped_css_rules[ $selector ][] = $prop . ': ' . $value . $unit . ' !important';
3898 - }
3899 - } else {
3900 - $grouped_css_rules[ $selector ][] = $property . ': ' . $value . $unit . ' !important';
3901 - }
3902 - }
3903 - }
3904 -
3905 - foreach ( $grouped_css_rules as $selector => $properties ) {
3906 - $css_rules[] = $selector . ' {' . implode( '; ', $properties ) . '}';
3907 - }
3908 -
3909 - $dynamic_css = implode( ' ', $css_rules );
3910 -
3911 - wp_cache_set( $cache_key, $dynamic_css, Cache::group(), 12 * HOUR_IN_SECONDS );
3912 - Cache::set_data_cache_key( $cache_key );
3913 -
3914 - if ( ! empty( $dynamic_css ) ) {
3915 - wp_add_inline_style( $style_handle, $dynamic_css );
3916 - }
3917 - }
3918 -
3919 - /**
3920 - * Pro version notice.
3921 - *
3922 - * @param string $ver Pro Version.
3923 - * @param string $tab Tab.
3924 - * @param string $name Name.
3925 - * @param bool $enable_free_Link Enable free link.
3926 - *
3927 - * @return array[]
3928 - */
3929 - public static function pro_version_notice( $ver, $tab = 'billing_fields', $name = 'ShopBuilder', $enable_free_Link = true ) {
3930 - return [
3931 - 'version_check' => [
3932 - 'id' => 'version_check',
3933 - 'type' => 'title',
3934 - 'label' => sprintf(
3935 - /* translators: 1: required version, 2: link to the Plugins page */
3936 - esc_html__(
3937 - 'To access all features and settings of this module, please ensure that "%1$s" is updated to version %2$s. %3$s',
3938 - 'shopbuilder'
3939 - ),
3940 - $name,
3941 - sprintf(
3942 - '<br /><u><b>%s</b></u>',
3943 - esc_html( $ver ?? '1.5.0' ) . ' or higher'
3944 - ),
3945 - $enable_free_Link
3946 - ? sprintf(
3947 - '%s <a class="pro-update-required" href="%s" target="_blank" title="%s">%s</a>',
3948 - esc_attr__( 'Update to the latest version', 'shopbuilder' ),
3949 - esc_url( admin_url( 'plugins.php' ) ),
3950 - esc_attr__( 'Go to the Plugin Page', 'shopbuilder' ),
3951 - esc_html__( 'from the Plugins page', 'shopbuilder' )
3952 - )
3953 - : ''
3954 - ),
3955 - 'tab' => $tab,
3956 - 'customClass' => 'checkout-notice',
3957 - ],
3958 - ];
3959 - }
3960 -
3961 - /**
3962 - * Get product gallery ids.
3963 - *
3964 - * @param object $product Product object.
3965 - *
3966 - * @return mixed
3967 - */
3968 - public static function get_cached_gallery_ids( $product ) {
3969 - $product_id = $product->get_id();
3970 - $cache_key = 'rtsb_product_gallery_ids_' . $product_id;
3971 -
3972 - if ( isset( self::$cache[ $cache_key ] ) ) {
3973 - return self::$cache[ $cache_key ];
3974 - }
3975 -
3976 - $cached_result = wp_cache_get( $cache_key, Cache::group() );
3977 -
3978 - if ( false !== $cached_result ) {
3979 - self::$cache[ $cache_key ] = $cached_result;
3980 -
3981 - return $cached_result;
3982 - }
3983 -
3984 - $gallery_ids = $product->get_gallery_image_ids();
3985 - self::$cache[ $cache_key ] = $gallery_ids;
3986 -
3987 - wp_cache_set( $cache_key, $gallery_ids, Cache::group(), 12 * HOUR_IN_SECONDS );
3988 - Cache::set_data_cache_key( $cache_key );
3989 -
3990 - return $gallery_ids;
3991 - }
3992 -
3993 - /**
3994 - * Check if optimization setting is turned on in the database.
3995 - *
3996 - * This only checks the user's setting value, without validating
3997 - * whether the cache directory is writable.
3998 - *
3999 - * @return bool
4000 - */
4001 - public static function is_optimization_setting_on() {
4002 - $has_pro = rtsb()->has_pro();
4003 - $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
4004 -
4005 - if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
4006 - return false;
4007 - }
4008 -
4009 - $generalList = GeneralList::instance()->get_data();
4010 -
4011 - return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
4012 - }
4013 -
4014 - /**
4015 - * Check if optimization is enabled and the cache directory is writable.
4016 - *
4017 - * @return bool
4018 - */
4019 - public static function is_optimization_enabled() {
4020 - // A bundle could not be built during this request, fall back to the
4021 - // unbundled assets rather than emitting handles with an empty source.
4022 - if ( self::$optimization_unavailable ) {
4023 - return false;
4024 - }
4025 -
4026 - if ( ! self::is_optimization_setting_on() ) {
4027 - return false;
4028 - }
4029 -
4030 - $upload = wp_upload_dir();
4031 - $cache_dir = trailingslashit( $upload['basedir'] ) . 'shopbuilder_uploads/cache/';
4032 -
4033 - if ( ! wp_is_writable( $cache_dir ) ) {
4034 - return false;
4035 - }
4036 -
4037 - return true;
4038 - }
4039 -
4040 - /**
4041 - * Get the optimized handle.
4042 - *
4043 - * @param string $handle Base handle used for script/style IDs.
4044 - *
4045 - * @return string
4046 - */
4047 - public static function optimized_handle( $handle ) {
4048 - $use_optimization = self::is_optimization_enabled();
4049 -
4050 - if ( $use_optimization ) {
4051 - $handle = self::is_contextual_loading() ? self::get_optimized_handle_by_context() : 'rtsb-bundled';
4052 - }
4053 -
4054 - return $handle;
4055 - }
4056 -
4057 - /**
4058 - * Get the optimized handle by context.
4059 - *
4060 - * @return string
4061 - */
4062 - public static function get_optimized_handle_by_context() {
4063 - $context = self::detect_context();
4064 -
4065 - if ( 'account' === $context ) {
4066 - return 'rtsb-bundled-global';
4067 - }
4068 -
4069 - return "rtsb-bundled-$context";
4070 - }
4071 -
4072 - /**
4073 - * Check if Elementor page.
4074 - *
4075 - * @param int $post_id Post ID.
4076 - *
4077 - * @return bool
4078 - */
4079 - public static function is_elementor_page( $post_id = null ) {
4080 - if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
4081 - return false;
4082 - }
4083 -
4084 - $post_id = $post_id ?: get_the_ID();
4085 -
4086 - if ( ! $post_id ) {
4087 - return true;
4088 - }
4089 -
4090 - return Plugin::$instance->documents->get( $post_id )->is_built_with_elementor();
4091 - }
4092 -
4093 - /**
4094 - * Check if shop or archive.
4095 - *
4096 - * @return bool
4097 - */
4098 - public static function is_shop_or_archive() {
4099 - return is_shop() || is_product_category() || is_product_tag() || is_post_type_archive( 'product' ) || BuilderFns::is_archive() || BuilderFns::is_shop() || is_tax( 'product_brand' );
4100 - }
4101 -
4102 - /**
4103 - * Detect context.
4104 - *
4105 - * @return string
4106 - */
4107 - public static function detect_context() {
4108 - switch ( true ) {
4109 - case is_product() || BuilderFns::is_product():
4110 - $context = 'product';
4111 - break;
4112 -
4113 - case is_cart() || BuilderFns::is_cart():
4114 - $context = 'cart';
4115 - break;
4116 -
4117 - case is_checkout() || BuilderFns::is_checkout():
4118 - $context = 'checkout';
4119 - break;
4120 -
4121 - case self::is_shop_or_archive():
4122 - $context = 'shop';
4123 - break;
4124 -
4125 - default:
4126 - $context = 'global';
4127 - break;
4128 - }
4129 -
4130 - return apply_filters( 'rtsb/optimizer/context_detect', $context );
4131 - }
4132 -
4133 - /**
4134 - * Enqueue optimized assets.
4135 - *
4136 - * @return void
4137 - */
4138 - public static function enqueue_optimized_assets() {
4139 - $asset_registry = AssetRegistry::instance();
4140 - $bundled_assets = $asset_registry->get_bundled_assets();
4141 - $context = self::detect_context();
4142 - $contextual_loading = self::is_contextual_loading();
4143 - $theme_handle = self::find_theme_stylesheet_handle();
4144 -
4145 - if ( $contextual_loading ) {
4146 - // Enqueue JS.
4147 - if ( ! empty( $bundled_assets['js'] ) ) {
4148 - $js_context = isset( $bundled_assets['js'][ $context ] ) ? $context : 'global';
4149 -
4150 - if ( $js_context ) {
4151 - wp_enqueue_script( $bundled_assets['js'][ $js_context ]['handle'] );
4152 - }
4153 - }
4154 -
4155 - // Enqueue CSS.
4156 - if ( ! empty( $bundled_assets['css'] ) ) {
4157 - $css_context = isset( $bundled_assets['css'][ $context ] ) ? $context : 'global';
4158 -
4159 - if ( $css_context ) {
4160 - $handle = $bundled_assets['css'][ $css_context ]['handle'];
4161 -
4162 - wp_enqueue_style( $handle );
4163 -
4164 - if ( ! empty( $theme_handle ) ) {
4165 - self::set_theme_dependency( $theme_handle, $handle );
4166 - }
4167 - }
4168 - }
4169 - } else {
4170 - // Enqueue JS.
4171 - if ( ! empty( $bundled_assets['js'] ) ) {
4172 - wp_enqueue_script( $bundled_assets['js']['handle'] );
4173 - }
4174 -
4175 - // Enqueue CSS.
4176 - if ( ! empty( $bundled_assets['css'] ) ) {
4177 - $handle = $bundled_assets['css']['handle'];
4178 -
4179 - wp_enqueue_style( $handle );
4180 -
4181 - if ( ! empty( $theme_handle ) ) {
4182 - self::set_theme_dependency( $theme_handle, $handle );
4183 - }
4184 - }
4185 - }
4186 - }
4187 -
4188 - /**
4189 - * Find theme stylesheet handle.
4190 - *
4191 - * @return string|false
4192 - */
4193 - private static function find_theme_stylesheet_handle() {
4194 - static $cached_handle = null;
4195 -
4196 - if ( null !== $cached_handle ) {
4197 - return $cached_handle;
4198 - }
4199 -
4200 - global $wp_styles;
4201 -
4202 - if ( ! $wp_styles instanceof WP_Styles ) {
4203 - return false;
4204 - }
4205 -
4206 - $stylesheet_uri = get_stylesheet_uri();
4207 - $theme_directory_uri = get_stylesheet_directory_uri();
4208 -
4209 - $hash = md5( $stylesheet_uri );
4210 - $transient_key = 'rtsb_theme_css_handle_' . $hash;
4211 -
4212 - $transient = get_transient( $transient_key );
4213 -
4214 - if ( false !== $transient ) {
4215 - $cached_handle = $transient;
4216 -
4217 - return $transient;
4218 - }
4219 -
4220 - foreach ( $wp_styles->registered as $handle => $style ) {
4221 - $src = $style->src;
4222 -
4223 - if (
4224 - ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
4225 - strpos( $src, 'style.css' ) !== false
4226 - ) {
4227 - set_transient( $transient_key, $handle, DAY_IN_SECONDS );
4228 - $cached_handle = $handle;
4229 -
4230 - return $handle;
4231 - }
4232 - }
4233 -
4234 - $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
4235 -
4236 - if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
4237 - set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
4238 - $cached_handle = $filtered_handle;
4239 -
4240 - return $filtered_handle;
4241 - }
4242 -
4243 - set_transient( $transient_key, false, DAY_IN_SECONDS );
4244 - $cached_handle = false;
4245 -
4246 - return false;
4247 - }
4248 -
4249 - /**
4250 - * Set theme dependency.
4251 - *
4252 - * @param string $theme_handle Theme handle.
4253 - * @param string $our_handle Our handle.
4254 - *
4255 - * @return void
4256 - */
4257 - private static function set_theme_dependency( $theme_handle, $our_handle ) {
4258 - global $wp_styles;
4259 -
4260 - if ( ! isset( $wp_styles->registered[ $theme_handle ] ) ) {
4261 - return;
4262 - }
4263 -
4264 - $theme_style = $wp_styles->registered[ $theme_handle ];
4265 -
4266 - // Add our handle as a dependency if it's not already there.
4267 - if ( ! in_array( $our_handle, $theme_style->deps, true ) ) {
4268 - $theme_style->deps[] = $our_handle;
4269 - }
4270 - }
4271 -
4272 - /**
4273 - * Check if Elementor scripts should be loaded.
4274 - *
4275 - * @return bool
4276 - */
4277 - public static function should_load_elementor_scripts() {
4278 - $data = GeneralList::instance()->get_data()['optimization'] ?? [];
4279 -
4280 - $enable_optimization = $data['enable_optimization'] ?? 'on';
4281 - $load_elementor_scripts = $data['load_elementor_scripts'] ?? 'on';
4282 -
4283 - if ( 'on' !== $enable_optimization ) {
4284 - return true;
4285 - }
4286 -
4287 - return 'on' === $load_elementor_scripts;
4288 - }
4289 -
4290 - /**
4291 - * Locate asset.
4292 - *
4293 - * @param string $relative_path Relative path.
4294 - *
4295 - * @return string|null
4296 - */
4297 - public static function locate_asset( $relative_path ) {
4298 - $free_context = rtsb();
4299 - $pro_context = function_exists( 'rtsbpro' ) && rtsb()->has_pro() ? rtsbpro() : null;
4300 -
4301 - $paths = [];
4302 -
4303 - if ( $pro_context ) {
4304 - $paths[] = $pro_context->get_assets_path( $relative_path );
4305 - }
4306 -
4307 - $paths[] = $free_context->get_assets_path( $relative_path );
4308 -
4309 - foreach ( $paths as $path ) {
4310 - if ( file_exists( $path ) ) {
4311 - return $path;
4312 - }
4313 - }
4314 -
4315 - return null;
4316 - }
4317 -
4318 - /**
4319 - * Enqueue module assets.
4320 - *
4321 - * @param string $handle Asset handle.
4322 - * @param string $module_name Module name.
4323 - * @param array $options Options array: ['type' => 'css|js|both', 'deps' => [], 'context' => null].
4324 - *
4325 - * @return string
4326 - */
4327 - public static function enqueue_module_assets( $handle, $module_name, $options = [] ) {
4328 - $use_optimization = self::is_optimization_enabled();
4329 - $handle = self::optimized_handle( $handle );
4330 -
4331 - if ( $use_optimization ) {
4332 - return $handle;
4333 - }
4334 -
4335 - $defaults = [
4336 - 'type' => 'both',
4337 - 'deps' => [ 'jquery', 'rtsb-public' ],
4338 - 'context' => null,
4339 - 'version' => RTSB_VERSION,
4340 - ];
4341 -
4342 - $config = array_merge( $defaults, $options );
4343 - $rtl_suffix = is_rtl() ? '-rtl' : '';
4344 - $rtl_dir = is_rtl() ? trailingslashit( 'rtl' ) : trailingslashit( 'css' );
4345 - $context = $config['context'] ?: rtsb();
4346 - $load_css = in_array( $config['type'], [ 'css', 'both' ], true );
4347 - $load_js = in_array( $config['type'], [ 'js', 'both' ], true );
4348 -
4349 - // Register CSS if enabled.
4350 - if ( $load_css ) {
4351 - wp_register_style(
4352 - $handle,
4353 - $context->get_assets_uri( $rtl_dir . 'modules/' . $module_name . $rtl_suffix . '.css' ),
4354 - [],
4355 - $config['version']
4356 - );
4357 - }
4358 -
4359 - // Register JS if enabled.
4360 - if ( $load_js ) {
4361 - wp_register_script(
4362 - $handle,
4363 - $context->get_assets_uri( 'js/modules/' . $module_name . '.js' ),
4364 - $config['deps'],
4365 - $config['version'],
4366 - true
4367 - );
4368 - }
4369 -
4370 - if ( $load_css ) {
4371 - wp_enqueue_style( $handle );
4372 -
4373 - $theme_handle = self::find_theme_stylesheet_handle();
4374 -
4375 - if ( ! empty( $theme_handle ) ) {
4376 - self::set_theme_dependency( $theme_handle, $handle );
4377 - }
4378 - }
4379 -
4380 - if ( $load_js ) {
4381 - wp_enqueue_script( $handle );
4382 - }
4383 -
4384 - return $handle;
4385 - }
4386 -
4387 - /**
4388 - * Check if contextual loading is enabled.
4389 - *
4390 - * @return bool
4391 - */
4392 - public static function is_contextual_loading() {
4393 - $data = GeneralList::instance()->get_data()['optimization'] ?? [];
4394 -
4395 - return ! empty( $data['context_asset_loading'] ) && 'on' === $data['context_asset_loading'];
4396 - }
4397 -
4398 - /**
4399 - * Get Modules list with cache support.
4400 - *
4401 - * @return array
4402 - */
4403 - public static function get_modules_list() {
4404 - static $cached_modules = null;
4405 -
4406 - if ( null !== $cached_modules ) {
4407 - return $cached_modules;
4408 - }
4409 -
4410 - $cache_key = 'rtsb_module_list';
4411 - $cache_group = Cache::group();
4412 -
4413 - $cached = wp_cache_get( $cache_key, $cache_group );
4414 -
4415 - if ( false !== $cached ) {
4416 - $cached_modules = $cached;
4417 -
4418 - return $cached;
4419 - }
4420 -
4421 - $modules = ModuleList::instance()->get_data();
4422 -
4423 - wp_cache_set( $cache_key, $modules, $cache_group, 12 * HOUR_IN_SECONDS );
4424 - Cache::set_data_cache_key( $cache_key );
4425 -
4426 - $cached_modules = $modules;
4427 -
4428 - return $modules;
4429 - }
4430 -
4431 - /**
4432 - * Get Elementor widgets list with cache support.
4433 - *
4434 - * @return array
4435 - */
4436 - public static function get_widgets_list() {
4437 - static $cached_widgets = null;
4438 -
4439 - if ( null !== $cached_widgets ) {
4440 - return $cached_widgets;
4441 - }
4442 -
4443 - $cache_key = 'rtsb_elementor_widget_list';
4444 - $cache_group = Cache::group();
4445 -
4446 - $cached = wp_cache_get( $cache_key, $cache_group );
4447 -
4448 - if ( false !== $cached ) {
4449 - $cached_widgets = $cached;
4450 -
4451 - return $cached;
4452 - }
4453 -
4454 - $widgets = ElementList::instance()->get_list();
4455 -
4456 - wp_cache_set( $cache_key, $widgets, $cache_group, 12 * HOUR_IN_SECONDS );
4457 - Cache::set_data_cache_key( $cache_key );
4458 -
4459 - $cached_widgets = $widgets;
4460 -
4461 - return $widgets;
4462 - }
4463 -
4464 - /**
4465 - * Convert the given price to the active currency.
4466 - *
4467 - * @param float $price The original price.
4468 - * @param Object||null $product Product.
4469 - *
4470 - * @return float
4471 - */
4472 - public static function get_currency_base_price( $price, $product = null ) {
4473 - return apply_filters( 'rtsb/convert/currency/price', $price, $product );
4474 - }
4475 - /**
4476 - * Generate a signed URL with a payload.
4477 - *
4478 - * @param array $payload Associative data to encode.
4479 - * @param string $base_url Base URL to append ?key=... (e.g. wc_get_checkout_url()).
4480 - * @param string $key Key name to use in the URL.
4481 - * @return string Signed URL with key parameter.
4482 - */
4483 - public static function generate_signed_url( array $payload, string $base_url, $key = 'key' ) {
4484 - if ( empty( $key ) ) {
4485 - $key = 'key';
4486 - }
4487 - // Convert payload to JSON.
4488 - $data = wp_json_encode( $payload );
4489 - // Create signature.
4490 - $signature = wp_hash( $data );
4491 - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
4492 - $encode_key = base64_encode( $data . '::' . $signature );
4493 - // Return full URL with key param.
4494 - return add_query_arg( [ $key => rawurlencode( $encode_key ) ], $base_url );
4495 - }
4496 -
4497 - /**
4498 - * Decode and verify a signed URL key.
4499 - *
4500 - * @param string $key Encoded key from URL.
4501 - * @return array|false Decoded payload array on success, false on failure.
4502 - */
4503 - public static function decode_signed_key( string $key ) {
4504 - if ( empty( $key ) ) {
4505 - return false;
4506 - }
4507 - $key = rawurldecode( wp_unslash( $key ) );
4508 - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
4509 - $raw = base64_decode( sanitize_text_field( $key ) );
4510 - if ( ! $raw ) {
4511 - return false;
4512 - }
4513 - $parts = explode( '::', $raw, 2 );
4514 - if ( count( $parts ) !== 2 ) {
4515 - return false;
4516 - }
4517 - list( $data_json, $signature ) = $parts;
4518 - // Validate signature.
4519 - if ( ! hash_equals( wp_hash( $data_json ), $signature ) ) {
4520 - return false;
4521 - }
4522 - $payload = json_decode( $data_json, true );
4523 - return is_array( $payload ) ? $payload : false;
4524 - }
4525 -
4526 - /**
4527 - * Get Loco Translate MO file path for a plugin.
4528 - *
4529 - * @param string $textdomain Plugin textdomain.
4530 - * @return void|false
4531 - */
4532 - public static function load_loco_textdomain( $textdomain ) {
4533 - if ( ! function_exists( 'loco_plugin_version' ) ) {
4534 - return false;
4535 - }
4536 - $lang = WP_LANG_DIR;
4537 - $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4538 - if ( ! file_exists( $path ) && defined( 'LOCO_LANG_DIR' ) ) {
4539 - $lang = LOCO_LANG_DIR;
4540 - $path = $lang . '/plugins/' . $textdomain . '-' . get_locale() . '.mo';
4541 - }
4542 - if ( ! file_exists( $path ) ) {
4543 - if ( 'shopbuilder' === $textdomain ) {
4544 - $plugin_root = dirname( RTSB_FILE );
4545 - } elseif ( 'shopbuilder-pro' === $textdomain ) {
4546 - $plugin_root = dirname( RTSBPRO_FILE );
4547 - } else {
4548 - return false;
4549 - }
4550 - $path = $plugin_root . '/languages/' . $textdomain . '-' . get_locale() . '.mo';
4551 - }
4552 - if ( ! file_exists( $path ) ) {
4553 - return false;
4554 - }
4555 - load_textdomain( $textdomain, $path );
4556 2324 }
4557 2325 }