PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
← All changes | app/Helpers/Fns.php +69 -518 3.4.23.2.6 View file →
@@ -12,11 +12,10 @@
12 12 exit( 'This script cannot be accessed directly.' );
13 13 }
14 14
15 15
16 -use RadiusTheme\SB\Dependencies\CodesVault\Howdyqb\DB;
16 +use CodesVault\Howdyqb\DB;
17 17 use DateTime;
18 -use WP_Query;
19 18 use WP_Styles;
20 19 use WC_Product;
21 20 use Elementor\Plugin;
22 21 use WC_Product_Query;
@@ -37,45 +36,9 @@
37 36 /**
38 37 * @var array
39 38 */
40 39 private static $cache = [];
41 -
42 40 /**
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 41 * Check if the stored license is valid. Its Only Show Pro Label.
79 42 *
80 43 * @return bool
81 44 */
@@ -280,20 +243,10 @@
280 243 *
281 244 * @return array|false|mixed
282 245 */
283 246 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 -
290 - if ( null !== $memo ) {
291 - return $memo;
292 - }
293 -
294 247 $cache_key = 'rtsb_get_user_roles';
295 - $roles = wp_cache_get( $cache_key, Cache::group() );
248 + $roles = wp_cache_get( $cache_key, 'shopbuilder' );
296 249
297 250 if ( empty( $roles ) ) {
298 251 if ( ! function_exists( 'get_editable_roles' ) ) {
299 252 require_once ABSPATH . 'wp-admin/includes/user.php';
@@ -307,18 +260,12 @@
307 260 continue;
308 261 }
309 262 $roles[ $key ] = $role['name'];
310 263 }
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 264 }
317 -
318 - $memo = $roles;
319 -
320 - return $memo;
265 + wp_cache_set( $cache_key, $roles, 'shopbuilder' );
266 + Cache::set_data_cache_key( $cache_key );
267 + return $roles;
321 268 }
322 269
323 270 /**
324 271 * Stripslashes value.
@@ -464,9 +411,9 @@
464 411 * @return false|string|void
465 412 */
466 413 public static function load_template( $template_name, $args = null, $return = false, $template_path = '', $plugin_path = '' ) {
467 414 $cache_key = sanitize_key( implode( '-', [ 'template', $template_name, $template_path ] ) );
468 - $located = (string) wp_cache_get( $cache_key, Cache::group() );
415 + $located = (string) wp_cache_get( $cache_key, 'shopbuilder' );
469 416 if ( ! $located ) {
470 417 $located = self::locate_template( $template_name, $template_path, $plugin_path );
471 418 // Don't cache the absolute path so that it can be shared between web servers with different paths.
472 419 $cache_path = wc_tokenize_path( $located, wc_get_path_define_tokens() );
@@ -475,14 +422,10 @@
475 422 // Make sure that the absolute path to the template is resolved.
476 423 $located = wc_untokenize_path( $located, wc_get_path_define_tokens() );
477 424 }
478 425 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 426 // translators: %s template.
484 - self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . esc_html( $reported ) . '</code>' ), '1.0' );
427 + self::doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'shopbuilder' ), '<code>' . $located . '</code>' ), '1.0' );
485 428
486 429 return;
487 430 }
488 431
@@ -611,15 +554,15 @@
611 554 }
612 555
613 556 global $wpdb;
614 557 $cache_key = 'rtsb_prepared_product_id';
615 - $_post_id = wp_cache_get( $cache_key, Cache::group() );
558 + $_post_id = wp_cache_get( $cache_key, 'shopbuilder' );
616 559 if ( false === $_post_id || 'publish' !== get_post_status( $_post_id ) ) {
617 560 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
618 561 $_post_id = $wpdb->get_var(
619 562 $wpdb->prepare( "SELECT MAX(ID) FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_status IN('publish', 'draft')", 'product' )
620 563 );
621 - wp_cache_set( $cache_key, $_post_id, Cache::group(), 12 * HOUR_IN_SECONDS );
564 + wp_cache_set( $cache_key, $_post_id, 'shopbuilder', 12 * HOUR_IN_SECONDS );
622 565 Cache::set_data_cache_key( $cache_key );
623 566 }
624 567
625 568 return $_post_id;
@@ -664,17 +607,8 @@
664 607 /**
665 608 * @return array
666 609 */
667 610 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 611 $pages = [];
678 612 $rawPages = get_pages();
679 613 if ( ! empty( $rawPages ) ) {
680 614 foreach ( $rawPages as $page ) {
@@ -681,11 +615,9 @@
681 615 $pages[ $page->ID ] = $page->post_title;
682 616 }
683 617 }
684 618
685 - $memo = $pages;
686 -
687 - return $memo;
619 + return $pages;
688 620 }
689 621
690 622 /**
691 623 * Get section items
@@ -1062,16 +994,8 @@
1062 994
1063 995 /**
1064 996 * Prints HTMl.
1065 997 *
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 998 * @param string $html HTML.
1075 999 * @param bool $allHtml All HTML.
1076 1000 *
1077 1001 * @return void
@@ -1080,9 +1004,9 @@
1080 1004 if ( ! $html ) {
1081 1005 return;
1082 1006 }
1083 1007 if ( $allHtml ) {
1084 - echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1008 + echo stripslashes_deep( $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1085 1009 } else {
1086 1010 echo wp_kses_post( stripslashes_deep( $html ) );
1087 1011 }
1088 1012 }
@@ -1311,18 +1235,8 @@
1311 1235 *
1312 1236 * @return array
1313 1237 */
1314 1238 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 1239 global $_wp_additional_image_sizes;
1326 1240
1327 1241 $sizes = [];
1328 1242
@@ -1348,11 +1262,9 @@
1348 1262
1349 1263 $imgSize['full'] = esc_html__( 'Full size', 'shopbuilder' );
1350 1264 $imgSize['rtsb_custom'] = esc_html__( 'Custom image size', 'shopbuilder' );
1351 1265
1352 - $memo = $imgSize;
1353 -
1354 - return $memo;
1266 + return $imgSize;
1355 1267 }
1356 1268
1357 1269 /**
1358 1270 * Free Layouts
@@ -1444,17 +1356,8 @@
1444 1356 *
1445 1357 * @return array
1446 1358 */
1447 1359 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 1360 $terms = [];
1458 1361 $termList = [];
1459 1362
1460 1363 $attributes = wc_get_attribute_taxonomies();
@@ -1474,10 +1377,8 @@
1474 1377 }
1475 1378 }
1476 1379 }
1477 1380
1478 - $cached = $terms;
1479 -
1480 1381 return $terms;
1481 1382 }
1482 1383
1483 1384 /**
@@ -1814,22 +1715,21 @@
1814 1715
1815 1716 /**
1816 1717 * Get Featured Image HTML.
1817 1718 *
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).
1719 + * @param string $type Image type.
1720 + * @param int $post_id Post ID.
1721 + * @param string $f_img_size Image size.
1722 + * @param null $default_img_id Default image ID.
1723 + * @param array $custom_img_size Custom image size.
1724 + * @param bool $lazy Lazy load check.
1725 + * @param bool $hover Hover image.
1726 + * @param bool $gallery Gallery image.
1727 + * @param bool $custom_image_id Custom category image ID.
1828 1728 *
1829 1729 * @return string|null
1830 1730 */
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 ) {
1731 + 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 ) {
1832 1732 $img_html = null;
1833 1733 $attachment_id = null;
1834 1734 $c_size = false;
1835 1735 $hover_class = '';
@@ -1868,19 +1768,8 @@
1868 1768 'class' => 'img-responsive rtsb-product-image' . $lazy_class . $hover_class,
1869 1769 'alt' => $alt_tag,
1870 1770 ];
1871 1771
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 1772 if ( $a_id ) {
1884 1773 $img_html = wp_get_attachment_image( $a_id, $f_img_size, false, $attr );
1885 1774 $attachment_id = $a_id;
1886 1775 }
@@ -2058,21 +1947,9 @@
2058 1947 public static function get_product_swatches( $type ) {
2059 1948 ?>
2060 1949 <div class="rtsb-swatches <?php echo esc_attr( $type ); ?>-layout">
2061 1950 <?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' ) ) {
1951 + if ( class_exists( 'Rtwpvsp' ) ) {
2075 1952 do_action( 'rtwpvs_show_archive_variation' );
2076 1953 } else {
2077 1954 do_action( 'rtsb/vs/showcase/variation' );
2078 1955 }
@@ -2100,18 +1977,20 @@
2100 1977 if ( ! $product->is_on_sale() ) {
2101 1978 return '';
2102 1979 }
2103 1980
1981 + $badge_text = '';
1982 + $percentage = self::calculate_sale_percentage( $product );
1983 +
1984 + if ( $percentage > 0 ) {
1985 + $badge_text = '-' . round( $percentage ) . '%';
1986 + }
1987 +
2104 1988 if ( 'text' === $type ) {
2105 - return $text;
1989 + $badge_text = $text;
2106 1990 }
2107 1991
2108 - $percentage = self::calculate_sale_percentage( $product );
2109 -
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;
1992 + return $badge_text;
2114 1993 }
2115 1994
2116 1995 /**
2117 1996 * Get sale badge
@@ -2146,18 +2025,20 @@
2146 2025 if ( ! $product->is_on_sale() ) {
2147 2026 return '';
2148 2027 }
2149 2028
2029 + $badge_text = '';
2030 + $percentage = self::calculate_sale_percentage( $product );
2031 +
2032 + if ( $percentage > 0 ) {
2033 + $badge_text = '-' . round( $percentage ) . '%';
2034 + }
2035 +
2150 2036 if ( 'text' === $type ) {
2151 - return $text;
2037 + $badge_text = $text;
2152 2038 }
2153 2039
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;
2040 + return $badge_text;
2160 2041 }
2161 2042
2162 2043 /**
2163 2044 * Calculate Sale Percentage
@@ -2201,41 +2082,27 @@
2201 2082
2202 2083 /**
2203 2084 * Pagination
2204 2085 *
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.
2086 + * @param string $pages Pages.
2087 + * @param integer $range Range.
2088 + * @param boolean $ajax Ajax.
2211 2089 *
2212 2090 * @return string
2213 2091 */
2214 - public static function custom_pagination( $pages = '', $per_page = 4, $ajax = false, $visible_range = null ) {
2215 - $html = null;
2092 + public static function custom_pagination( $pages = '', $range = 4, $ajax = false ) {
2093 + $html = null;
2094 + $visible_range = apply_filters( 'rtsb/elements/pagination/visible_range', ( $range * 2 ) + 1 );
2216 2095
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 2096 global $paged;
2229 2097
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
2098 + if ( is_front_page() ) {
2099 + $paged = ( get_query_var( 'page' ) ) ? get_query_var( 'page' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2100 + } else {
2101 + $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2236 2102 }
2237 - if ( ! $paged ) {
2103 +
2104 + if ( empty( $paged ) ) {
2238 2105 $paged = 1; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
2239 2106 }
2240 2107
2241 2108 if ( '' === $pages ) {
@@ -2280,9 +2147,9 @@
2280 2147 $p = $paged + 1;
2281 2148 $html .= "<li><a data-paged='{$p}' href=\"" . get_pagenum_link( $paged + 1 ) . "\" aria-label='Next'>&rsaquo;</a></li>";
2282 2149 }
2283 2150
2284 - if ( $paged < $pages - 1 && $paged + $visible_range < $pages && $visible_range < $pages ) {
2151 + if ( $paged < $pages - 1 && $paged + $range - 1 < $pages && $visible_range < $pages ) {
2285 2152 $html .= "<li><a data-paged='{$pages}' href='" . get_pagenum_link( $pages ) . "' aria-label='Last'>&raquo;</a></li>";
2286 2153 }
2287 2154
2288 2155 $html .= '</ul>';
@@ -2674,30 +2541,8 @@
2674 2541 return true;
2675 2542 }
2676 2543 }
2677 2544
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 2545 /***
2701 2546 * Save Settings data
2702 2547 *
2703 2548 * @param string $section_id Section ID.
@@ -2828,44 +2673,8 @@
2828 2673 return $results;
2829 2674 }
2830 2675
2831 2676 /**
2832 - * Count published products matching a tax query.
2833 - *
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).
2840 - *
2841 - * @param array $tax_query Tax query clauses.
2842 - *
2843 - * @return int Number of matching products.
2844 - */
2845 - public static function count_products_by_tax_query( array $tax_query ) {
2846 - if ( empty( $tax_query ) ) {
2847 - return 0;
2848 - }
2849 -
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 2677 * Count products by taxonomies.
2869 2678 *
2870 2679 * @param array $terms Terms.
2871 2680 * @param string $taxonomy Taxonomy.
@@ -2983,233 +2792,8 @@
2983 2792 return ! empty( $ajax[0] );
2984 2793 }
2985 2794
2986 2795 /**
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 2796 * Check if product has applied filter.
3213 2797 *
3214 2798 * @param string $page Page name.
3215 2799 *
@@ -3230,15 +2814,8 @@
3230 2814 $ajax[] = ! isset( $data['settings']['active_filter'] );
3231 2815
3232 2816 }
3233 2817
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 2818 return ! empty( $ajax[0] );
3242 2819 }
3243 2820
3244 2821 /**
@@ -3257,9 +2834,9 @@
3257 2834
3258 2835 if ( isset( self::$cache[ $cache_key ] ) ) {
3259 2836 return self::$cache[ $cache_key ];
3260 2837 }
3261 - $results = wp_cache_get( $cache_key, Cache::group() );
2838 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
3262 2839 if ( ! $results ) {
3263 2840 global $wpdb;
3264 2841 $sql = "SELECT t.term_id, t.name
3265 2842 FROM {$wpdb->terms} t
@@ -3274,9 +2851,9 @@
3274 2851 }
3275 2852
3276 2853 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
3277 2854 // Cache the results in the object cache for future use.
3278 - wp_cache_set( $cache_key, $results, Cache::group(), 12 * HOUR_IN_SECONDS );
2855 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
3279 2856 Cache::set_data_cache_key( $cache_key );
3280 2857 }
3281 2858
3282 2859 $cats = [];
@@ -3313,9 +2890,9 @@
3313 2890
3314 2891 if ( isset( self::$cache[ $cache_key ] ) ) {
3315 2892 return self::$cache[ $cache_key ];
3316 2893 }
3317 - $results = wp_cache_get( $cache_key, Cache::group() );
2894 + $results = wp_cache_get( $cache_key, 'shopbuilder' );
3318 2895 if ( ! $results ) {
3319 2896 global $wpdb;
3320 2897 $sql = "SELECT t.term_id, t.name
3321 2898 FROM {$wpdb->terms} t
@@ -3330,9 +2907,9 @@
3330 2907 }
3331 2908
3332 2909 $results = $wpdb->get_results( $prepared_sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
3333 2910 // Cache the results in the object cache for future use.
3334 - wp_cache_set( $cache_key, $results, Cache::group(), 12 * HOUR_IN_SECONDS );
2911 + wp_cache_set( $cache_key, $results, 'shopbuilder' ); // Adjust the expiration time as needed.
3335 2912 Cache::set_data_cache_key( $cache_key );
3336 2913 }
3337 2914
3338 2915 $cats = [];
@@ -3869,9 +3446,9 @@
3869 3446 *
3870 3447 * @return void
3871 3448 */
3872 3449 public static function dynamic_styles( $options, $cache_key, $css_properties ) {
3873 - $cached_css = wp_cache_get( $cache_key, Cache::group() );
3450 + $cached_css = wp_cache_get( $cache_key, 'shopbuilder' );
3874 3451 $style_handle = self::optimized_handle( 'rtsb-frontend' );
3875 3452
3876 3453 if ( false !== $cached_css ) {
3877 3454 wp_add_inline_style( $style_handle, $cached_css );
@@ -3907,9 +3484,9 @@
3907 3484 }
3908 3485
3909 3486 $dynamic_css = implode( ' ', $css_rules );
3910 3487
3911 - wp_cache_set( $cache_key, $dynamic_css, Cache::group(), 12 * HOUR_IN_SECONDS );
3488 + wp_cache_set( $cache_key, $dynamic_css, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3912 3489 Cache::set_data_cache_key( $cache_key );
3913 3490
3914 3491 if ( ! empty( $dynamic_css ) ) {
3915 3492 wp_add_inline_style( $style_handle, $dynamic_css );
@@ -3972,9 +3549,9 @@
3972 3549 if ( isset( self::$cache[ $cache_key ] ) ) {
3973 3550 return self::$cache[ $cache_key ];
3974 3551 }
3975 3552
3976 - $cached_result = wp_cache_get( $cache_key, Cache::group() );
3553 + $cached_result = wp_cache_get( $cache_key, 'shopbuilder' );
3977 3554
3978 3555 if ( false !== $cached_result ) {
3979 3556 self::$cache[ $cache_key ] = $cached_result;
3980 3557
@@ -3983,9 +3560,9 @@
3983 3560
3984 3561 $gallery_ids = $product->get_gallery_image_ids();
3985 3562 self::$cache[ $cache_key ] = $gallery_ids;
3986 3563
3987 - wp_cache_set( $cache_key, $gallery_ids, Cache::group(), 12 * HOUR_IN_SECONDS );
3564 + wp_cache_set( $cache_key, $gallery_ids, 'shopbuilder', 12 * HOUR_IN_SECONDS );
3988 3565 Cache::set_data_cache_key( $cache_key );
3989 3566
3990 3567 return $gallery_ids;
3991 3568 }
@@ -3990,16 +3567,13 @@
3990 3567 return $gallery_ids;
3991 3568 }
3992 3569
3993 3570 /**
3994 - * Check if optimization setting is turned on in the database.
3571 + * Check if optimization settings are enabled.
3995 3572 *
3996 - * This only checks the user's setting value, without validating
3997 - * whether the cache directory is writable.
3998 - *
3999 3573 * @return bool
4000 3574 */
4001 - public static function is_optimization_setting_on() {
3575 + public static function is_optimization_enabled() {
4002 3576 $has_pro = rtsb()->has_pro();
4003 3577 $pro_ver = defined( 'RTSBPRO_VERSION' ) ? RTSBPRO_VERSION : 0;
4004 3578
4005 3579 if ( $has_pro && version_compare( $pro_ver, '2.0.0', '<' ) ) {
@@ -4011,34 +3585,8 @@
4011 3585 return 'on' === ( $generalList['optimization']['enable_optimization'] ?? '' );
4012 3586 }
4013 3587
4014 3588 /**
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 3589 * Get the optimized handle.
4042 3590 *
4043 3591 * @param string $handle Base handle used for script/style IDs.
4044 3592 *
@@ -4224,8 +3772,9 @@
4224 3772 ( $src === $stylesheet_uri || strpos( $src, $theme_directory_uri ) !== false ) &&
4225 3773 strpos( $src, 'style.css' ) !== false
4226 3774 ) {
4227 3775 set_transient( $transient_key, $handle, DAY_IN_SECONDS );
3776 + Cache::set_transient_cache_key( $transient_key );
4228 3777 $cached_handle = $handle;
4229 3778
4230 3779 return $handle;
4231 3780 }
@@ -4234,8 +3783,9 @@
4234 3783 $filtered_handle = apply_filters( 'rtsb/optimizer/theme_stylesheet_handle', false );
4235 3784
4236 3785 if ( is_string( $filtered_handle ) && ! empty( $filtered_handle ) ) {
4237 3786 set_transient( $transient_key, $filtered_handle, DAY_IN_SECONDS );
3787 + Cache::set_transient_cache_key( $transient_key );
4238 3788 $cached_handle = $filtered_handle;
4239 3789
4240 3790 return $filtered_handle;
4241 3791 }
@@ -4240,8 +3790,9 @@
4240 3790 return $filtered_handle;
4241 3791 }
4242 3792
4243 3793 set_transient( $transient_key, false, DAY_IN_SECONDS );
3794 + Cache::set_transient_cache_key( $transient_key );
4244 3795 $cached_handle = false;
4245 3796
4246 3797 return false;
4247 3798 }
@@ -4407,9 +3958,9 @@
4407 3958 return $cached_modules;
4408 3959 }
4409 3960
4410 3961 $cache_key = 'rtsb_module_list';
4411 - $cache_group = Cache::group();
3962 + $cache_group = 'shopbuilder';
4412 3963
4413 3964 $cached = wp_cache_get( $cache_key, $cache_group );
4414 3965
4415 3966 if ( false !== $cached ) {
@@ -4440,9 +3991,9 @@
4440 3991 return $cached_widgets;
4441 3992 }
4442 3993
4443 3994 $cache_key = 'rtsb_elementor_widget_list';
4444 - $cache_group = Cache::group();
3995 + $cache_group = 'shopbuilder';
4445 3996
4446 3997 $cached = wp_cache_get( $cache_key, $cache_group );
4447 3998
4448 3999 if ( false !== $cached ) {